Merge remote-tracking branch 'origin/GP-4970-dragonmacher-help-fix'

This commit is contained in:
Ryan Kurtz 2024-10-04 07:38:12 -04:00
commit 6315aa68df
20 changed files with 261 additions and 159 deletions

View File

@ -1,4 +1,7 @@
<HTML>
<HEAD>
<TITLE>Ghidra User Agreement</TITLE>
</HEAD>
<FONT SIZE="5">

View File

@ -66,7 +66,7 @@ def installPoint = "../help/help"
/**
* Build the pdf docs for BSim and place into the '$installPoint' directory.
* A build (ex: 'gradle buildLocalTSSI_Release') will place the pdf in the distribution.
* A build (ex: 'gradle buildLocalPublic_Release') will place the pdf in the distribution.
* There is an associated, auto-generated clean task.
**/
task buildBSimHelpPdf(type: Exec) {
@ -136,7 +136,7 @@ task buildBSimHelpPdf(type: Exec) {
/**
* Build the html docs for BSim and place into the '$installPoint' directory.
* A build (ex: 'gradle buildLocalTSSI_Release') will place the html files in the distribution.
* A build (ex: 'gradle buildLocalPublic_Release') will place the html files in the distribution.
**/
task buildBSimHelpHtml(type: Exec) {

View File

@ -140,11 +140,19 @@ tasks.register('generateExtraHelpFiles') {
}
}
// Base's help includes the file generated by the 'generateExtraHelpFiles' task. Signal that we
// depend on that task and it's output file.
tasks.named('buildHelp') {
tasks.named('buildHelpFiles') {
dependsOn(tasks.named('generateExtraHelpFiles'))
inputs.files tasks.named('generateExtraHelpFiles').get().outputs
doFirst {
// Add the generated help path to the args passed to GHelpBuilder. Add the build dir that
// contains the file generated in 'generateExtraHelpFiles':
// def htmlTipsFile = file('build/help/main/help/topics/Misc/Tips.htm')
// This file is needed to correctly generate help ID -> file mappings.
args "-hpg"
args file('build/help/main/help').toString()
}
}
def createTipsHelpFile(input, output) {

View File

@ -379,7 +379,7 @@
<tocdef id="Keyboard Navigation" sortgroup="gg" text="Keyboard Navigation" target="help/topics/KeyboardNavigation/KeyboardNavigation.html"/>
<tocdef id="Undo/Redo" sortgroup="h" text="Undo/Redo" target="help/topics/Tool/Undo_Redo.htm" />
<tocdef id="Glossary" sortgroup="i" text="Glossary" target="help/topics/Glossary/glossary.htm" />
<tocdef id="What's New" sortgroup="j" text="What's New" target="docs/WhatsNew.html" />
<tocdef id="What's New" sortgroup="j" text="What's New" target="external:docs/WhatsNew.html" />
<tocdef id="Tips of the Day" sortgroup="k" text="Tips of the Day" target="help/topics/Misc/Tips.htm" />
<tocdef id="Appendix" sortgroup="z" text="Appendix" target="help/topics/Misc/Appendix.htm">
<tocdef id="Block Models" sortgroup="a" text="Block Models" target="help/topics/BlockModel/Block_Model.htm" />

View File

@ -75,7 +75,7 @@ Did you know you can see where a register is initialized in its current scope by
You can perform a program memory search using a regular expression (regex).
If a Windows executable contains Icons or Bitmap Resources, they are displayed in the CodeBrowser. Do a Search->Program Text on Labels for "Rsrc_Icon*" and "Rsrc_Bitmap*" to find them.
The average temperature on Earth is 15 degrees celsius.
The average temperature on Earth is 15 degrees Celsius.
If you hover on a reference in the XREF or operand fields, a popup with the reference code or data will appear.
If you hover on a data type in the CodeBrowser or Data Type Manager, a popup with the data type definition will appear.
@ -100,8 +100,10 @@ Ghidra allows full customization of all colors, fonts and icons via Edit->Theme-
You can quickly change the font size of the Listing, Decompiler or the Bytes windows by pressing Ctrl-+ or Ctrl-- while inside of those windows.
You can quickly control the font size from the Theme Editor dialog via Edit->Theme->Configure.
You can quickly control the font size from the Theme Editor dialog's toolbar via Edit->Theme->Configure.
You can create a table whose rows correspond to the address ranges in a selection via Select->Create Table From Ranges.
The tab and title of any Search Results window can be renamed by right-clicking and choosing 'Rename'.
This is the last tip. You can turn them off now.

View File

@ -52,7 +52,7 @@
<tocref id="Program Annotation">
<tocdef id="PDB" sortgroup="q" text="PDB" target="help/topics/Pdb/PDB.htm" >
<tocdef id="LoadPDB" sortgroup="a" text="Load PDB File" target="help/topics/Pdb/LoadPDB.html" />
<tocdef id="README_PDB" sortgroup="b" text="PDB Parser (README_PDB)" target="docs/README_PDB.html" />
<tocdef id="README_PDB" sortgroup="b" text="PDB Parser (README_PDB)" target="external:docs/README_PDB.html" />
</tocdef>
</tocref>
</tocroot>

View File

@ -102,7 +102,7 @@
</UL></BLOCKQUOTE>
</P>
<P><B>NOTE:</B> Execution of <i>pdb.exe</i> has runtime dependencies which must be satisfied.
Please refer to the <a href="docs/README_PDB.html">README_PDB</a> document for details.</P>
Please refer to the <a href="external:docs/README_PDB.html">README_PDB</a> document for details.</P>
<H2><A name="dia"></A>Debug Interface Access SDK</H2>
@ -118,7 +118,7 @@
<P><IMG src="help/shared/note.png" border="0">If you are attempting to load a PDB on a
Windows machine and see an error message such as &quot;Unable to locate the DIA SDK,&quot;
you will need to add and register one or more files on your computer. Refer to the
<a href="docs/README_PDB.html">README_PDB</a> document for detailed instructions.
<a href="external:docs/README_PDB.html">README_PDB</a> document for detailed instructions.
</P>
</BLOCKQUOTE>
<P class="relatedtopic">Related Topics:</P>

View File

@ -830,6 +830,7 @@ public class Application {
/**
* Returns a collection of module library directories. Library directories are optional for a module.
* @return a collection of module library directories.
* @see ModuleUtilities#getModuleLibDirectories(Collection)
*/
public static Collection<ResourceFile> getLibraryDirectories() {
checkAppInitialized();

View File

@ -51,12 +51,14 @@ public class GHelpBuilder {
private static final String OUTPUT_DIRECTORY_OPTION = "-o";
private static final String MODULE_NAME_OPTION = "-n";
private static final String HELP_PATHS_OPTION = "-hp";
private static final String HELP_PATHS_GENERATED_OPTION = "-hpg";
private static final String DEBUG_SWITCH = "-debug";
private static final String IGNORE_INVALID_SWITCH = "-ignoreinvalid";
private String outputDirectoryName;
private String moduleName;
private Collection<File> dependencyHelpPaths = new LinkedHashSet<>();
private Collection<File> generatedDependencyHelpPaths = new LinkedHashSet<>();
private Collection<File> helpInputDirectories = new LinkedHashSet<>();
private static boolean debugEnabled = false;
private boolean ignoreInvalid = false; // TODO: Do actual validation here
@ -112,7 +114,22 @@ public class GHelpBuilder {
for (File file : dependencyHelpPaths) {
allHelp.add(file);
}
return HelpModuleCollection.fromFiles(allHelp);
HelpModuleCollection help = HelpModuleCollection.fromFiles(allHelp);
for (File file : generatedDependencyHelpPaths) {
help.addGeneratedHelpLocation(file);
}
return help;
}
private HelpModuleCollection collectInputHelp() {
HelpModuleCollection help = HelpModuleCollection.fromFiles(helpInputDirectories);
for (File file : generatedDependencyHelpPaths) {
help.addGeneratedHelpLocation(file);
}
return help;
}
private Results validateHelpDirectories(HelpModuleCollection help, LinkDatabase linkDatabase) {
@ -163,11 +180,11 @@ public class GHelpBuilder {
JavaHelpFilesBuilder fileBuilder =
new JavaHelpFilesBuilder(outputDirectory, moduleName, linkDatabase);
HelpModuleCollection help = HelpModuleCollection.fromFiles(helpInputDirectories);
HelpModuleCollection helpInput = collectInputHelp();
// 1) Generate JavaHelp files for the module (e.g., TOC file, map file)
try {
fileBuilder.generateHelpFiles(help);
fileBuilder.generateHelpFiles(helpInput);
}
catch (Exception e) {
exitWithError("Unexpected error building help module files:\n", e);
@ -302,6 +319,20 @@ public class GHelpBuilder {
}
}
}
else if (opt.equals(HELP_PATHS_GENERATED_OPTION)) {
i++;
if (i >= args.length) {
errorMessage(HELP_PATHS_GENERATED_OPTION + " requires an argument");
printUsage();
System.exit(1);
}
String hp = args[i];
if (hp.length() > 0) {
for (String p : hp.split(File.pathSeparator)) {
generatedDependencyHelpPaths.add(new File(p));
}
}
}
else if (opt.equals(DEBUG_SWITCH)) {
debugEnabled = true;
}

View File

@ -21,7 +21,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -36,7 +37,6 @@ import javax.swing.text.html.HTML.Tag;
import generic.jar.ResourceFile;
import generic.theme.GColor;
import generic.theme.Gui;
import ghidra.framework.Application;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
import resources.*;
@ -183,6 +183,10 @@ public class GHelpHTMLEditorKit extends HTMLEditorKit {
*/
private HyperlinkEvent validateURL(HyperlinkEvent event) {
URL url = event.getURL();
if (url == null) {
Msg.trace(this, "No URL for link: " + event);
return maybeCreateNewHyperlinkEventWithUpdatedURL(event);
}
try {
url.openStream();// assume that this will fail if the file does not exist
}
@ -239,7 +243,7 @@ public class GHelpHTMLEditorKit extends HTMLEditorKit {
// directory). See if it may be a relative link to a build's installation root (like
// a file in <install dir>/docs).
//
newUrl = findApplicationfile(HREF);
newUrl = HelpBuildUtils.findApplicationUrl(HREF);
return newUrl;
}
@ -385,37 +389,15 @@ public class GHelpHTMLEditorKit extends HTMLEditorKit {
return url;
}
return findModuleFile("help/shared/" + name);
}
private URL findApplicationfile(String relativePath) {
ResourceFile installDir = Application.getInstallationDirectory();
ResourceFile file = new ResourceFile(installDir, relativePath);
if (file.exists()) {
ResourceFile file = HelpBuildUtils.findModuleFile("help/shared/" + name);
if (file != null) {
try {
return file.toURL();
}
catch (MalformedURLException e) {
Msg.showError(this, null, "Unexpected Error",
"Unexpected error parsing file to URL: " + file);
}
}
return null;
}
private URL findModuleFile(String relativePath) {
Collection<ResourceFile> moduleDirs = Application.getModuleRootDirectories();
for (ResourceFile dir : moduleDirs) {
ResourceFile file = new ResourceFile(dir, relativePath);
if (file.exists()) {
try {
return file.toURL();
}
catch (MalformedURLException e) {
Msg.showError(this, null, "Unexpected Error",
"Unexpected error parsing file to URL: " + file);
return null;
}
return null;
}
}
return null;

View File

@ -28,9 +28,8 @@ import javax.help.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.util.SystemUtilities;
import help.validator.JavaHelpValidator;
/**
* Ghidra help set that creates a GhidraHelpBroker, installs some custom HTML handling code via
@ -201,17 +200,22 @@ public class GHelpSet extends HelpSet {
/**
* This is meant for help files that are not included in the standard help system. Their
* id paths are expected to be relative to the application install directory.
* @param id the help id.
* @param rawId the help id.
* @return the URL to the help file.
*/
private URL tryToCreateURLFromID(String id) {
private URL tryToCreateURLFromID(String rawId) {
URL fileURL = createFileURL(id);
if (fileURL != null) {
return fileURL;
String idText = rawId;
if (rawId.startsWith(JavaHelpValidator.EXTERNAL_PREFIX)) {
idText = rawId.substring(JavaHelpValidator.EXTERNAL_PREFIX.length());
}
URL rawURL = createRawURL(id);
URL fileUrl = HelpBuildUtils.findApplicationUrl(idText);
if (fileUrl != null) {
return fileUrl;
}
URL rawURL = createRawURL(idText);
return rawURL;
}
@ -238,31 +242,6 @@ public class GHelpSet extends HelpSet {
return null;
}
private URL createFileURL(String id) {
ResourceFile helpFile = fileFromID(id);
if (!helpFile.exists()) {
LOG.trace("ID is not a file; tried: " + helpFile);
return null;
}
try {
return helpFile.toURL();
}
catch (MalformedURLException e) {
// this shouldn't happen, as the file exists
LOG.trace("ID is not a URL; tried to make URL from file: " + helpFile);
}
return null;
}
private ResourceFile fileFromID(String id) {
// this allows us to find files by using relative paths (e.g., 'docs/WhatsNew.html'
// will get resolved relative to the installation directory in a build).
ResourceFile installDir = Application.getInstallationDirectory();
ResourceFile helpFile = new ResourceFile(installDir, id);
return helpFile;
}
@Override
public boolean isID(URL url) {
return mapDelegate.isID(url);

View File

@ -30,7 +30,8 @@ import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.Gui;
import ghidra.framework.Application;
import ghidra.util.HelpLocation;
import ghidra.util.*;
import help.validator.JavaHelpValidator;
import help.validator.location.*;
import resources.IconProvider;
import resources.Icons;
@ -166,6 +167,71 @@ public class HelpBuildUtils {
return null;
}
/**
* Finds the actual module file for the given relative path when in development mode. For
* example, given:
* <pre>
* help/shared/DefaultStyle.css
* </pre>
* This method will find:
* <pre>
* {repo}/Ghidra/Framework/Help/src/main/resources/help/shared/DefaultStyle.css
* </pre>
* @param relativePath the path
* @return the file
*/
public static ResourceFile findModuleFile(String relativePath) {
Collection<ResourceFile> moduleDirs = Application.getModuleRootDirectories();
for (ResourceFile dir : moduleDirs) {
ResourceFile file = new ResourceFile(dir, relativePath);
if (file.exists()) {
return file;
}
}
return null;
}
/**
* Searches the application classpath (for a module file) and directory structure (for a release
* file), depending on whether in release mode or development mode, to find the URL for the
* given relative path.
* @param relativePath the path
* @return the URL or null if not found
*/
public static URL findApplicationUrl(String relativePath) {
String updatedPath = relativePath;
if (relativePath.startsWith(JavaHelpValidator.EXTERNAL_PREFIX)) {
updatedPath = relativePath.substring(JavaHelpValidator.EXTERNAL_PREFIX.length());
}
ResourceFile file = null;
if (SystemUtilities.isInDevelopmentMode()) {
// example: "docs/WhatsNew.html", which lives in a source dir in dev mode
file = findModuleFile("src/global/" + updatedPath);
}
else {
//
// In release mode, some of the help content is relative to the root of the application,
// such as 'docs/WhatsNew.html'.
//
ResourceFile installDir = Application.getInstallationDirectory();
file = new ResourceFile(installDir, updatedPath);
}
if (file == null || !file.exists()) {
return null;
}
try {
return file.toURL();
}
catch (MalformedURLException e) {
Msg.trace(HelpBuildUtils.class, "Unexpected error parsing file to URL: " + file);
}
return null;
}
//==================================================================================================
// Cleanup Methods
//==================================================================================================

View File

@ -220,6 +220,7 @@ public class JavaHelpFilesBuilder {
Files.delete(file);
}
catch (IOException e) {
// ignore
}
}
}

View File

@ -29,23 +29,13 @@ import help.validator.location.HelpModuleCollection;
import help.validator.model.*;
public class JavaHelpValidator {
// This allows help links to signal that the reference is pointing to a file that lives outside
// of the help system
public static final String EXTERNAL_PREFIX = "external:";
private static boolean debug;
/** Files that are generated and may not exist at validation time */
private static Set<String> EXCLUDED_FILE_NAMES = createExcludedFileSet();
private static Set<String> createExcludedFileSet() {
Set<String> set = new HashSet<>();
// The expected format is the help path, without an extension (this helps catch multiple
// references with anchors)
set.add("help/topics/Misc/Tips");
set.add("docs/WhatsNew");
set.add("docs/README_PDB");
return set;
}
private String moduleName;
private HelpModuleCollection help;
@ -304,7 +294,7 @@ public class JavaHelpValidator {
path = path.substring(0, index);
}
return EXCLUDED_FILE_NAMES.contains(path);
return path.startsWith(EXTERNAL_PREFIX);
}
private void validateExternalFileLinks(LinkDatabase linkDatabase) {

View File

@ -15,6 +15,12 @@
*/
package help.validator;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.Map.Entry;
import help.OverlayHelpTree;
import help.TOCItemProvider;
import help.validator.links.InvalidHREFLink;
@ -22,12 +28,6 @@ import help.validator.links.InvalidLink;
import help.validator.location.HelpModuleCollection;
import help.validator.model.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.Map.Entry;
public class LinkDatabase {
/** Sorted for later presentation */

View File

@ -0,0 +1,43 @@
/* ###
* 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.
*/
package help.validator.location;
import java.io.File;
import help.validator.model.GhidraTOCFile;
/**
* Represents a directory that holds generated content. At the time of writing, the only known
* such input is the 'tips of the day' html file that is created from a text file.
*/
public class GeneratedDirectoryHelpModuleLocation extends DirectoryHelpModuleLocation {
public GeneratedDirectoryHelpModuleLocation(File file) {
super(file);
}
@Override
public GhidraTOCFile loadSourceTOCFile() {
// Generated directories are not full help directories with TOC source files.
return null;
}
@Override
public boolean isHelpInputSource() {
return false;
}
}

View File

@ -16,21 +16,10 @@
package help.validator.location;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import javax.help.HelpSet;
@ -38,19 +27,9 @@ import javax.help.Map.ID;
import javax.help.TOCView;
import javax.swing.tree.DefaultMutableTreeNode;
import help.*;
import help.CustomTOCView.CustomTreeItemDecorator;
import help.HelpBuildUtils;
import help.PathKey;
import help.TOCItemProvider;
import help.validator.model.AnchorDefinition;
import help.validator.model.GhidraTOCFile;
import help.validator.model.HREF;
import help.validator.model.HelpFile;
import help.validator.model.HelpTopic;
import help.validator.model.IMG;
import help.validator.model.TOCItem;
import help.validator.model.TOCItemDefinition;
import help.validator.model.TOCItemExternal;
import help.validator.model.*;
/**
* A class that is meant to hold a single help <b>input</b> directory and 0 or more
@ -130,12 +109,21 @@ public class HelpModuleCollection implements TOCItemProvider {
if (inputHelp == null && externalHelpSets.size() == 0) {
throw new IllegalArgumentException(
"Required TOC file does not exist. " + "You must create a TOC_Source.xml file, " +
"Required TOC file does not exist. You must create a TOC_Source.xml file, " +
"even if it is an empty template, or provide a pre-built TOC. " +
"Help directories: " + locations.toString());
}
}
public void addGeneratedHelpLocation(File file) {
HelpModuleLocation location = new GeneratedDirectoryHelpModuleLocation(file);
helpLocations.add(location);
HelpSet helpSet = location.getHelpSet();
if (helpSet != null) {
externalHelpSets.add(helpSet);
}
}
public GhidraTOCFile getSourceTOCFile() {
return inputHelp.getSourceTOCFile();
}
@ -160,17 +148,17 @@ public class HelpModuleCollection implements TOCItemProvider {
externalHelpSets = new ArrayList<>();
for (HelpModuleLocation location : helpLocations) {
if (location.isHelpInputSource()) {
continue; // help sets only exist in pre-built help
}
doAddHelpSet(location);
}
}
HelpSet helpSet = location.getHelpSet();
externalHelpSets.add(helpSet);
private void doAddHelpSet(HelpModuleLocation location) {
if (location.isHelpInputSource()) {
return; // help sets only exist in pre-built help
}
if (externalHelpSets.isEmpty()) {
return;
}
HelpSet helpSet = location.getHelpSet();
externalHelpSets.add(helpSet);
}
public boolean containsHelpFiles() {
@ -230,7 +218,8 @@ public class HelpModuleCollection implements TOCItemProvider {
public Collection<AnchorDefinition> getAllAnchorDefinitions() {
List<AnchorDefinition> result = new ArrayList<>();
for (HelpModuleLocation location : helpLocations) {
result.addAll(location.getAllAnchorDefinitions());
Collection<AnchorDefinition> anchors = location.getAllAnchorDefinitions();
result.addAll(anchors);
}
return result;
}
@ -250,7 +239,6 @@ public class HelpModuleCollection implements TOCItemProvider {
if (helpPath == null) {
return null;
}
Map<PathKey, HelpFile> map = getPathHelpFileMap();
return map.get(new PathKey(helpPath));
}
@ -373,4 +361,5 @@ public class HelpModuleCollection implements TOCItemProvider {
public String toString() {
return helpLocations.toString();
}
}

View File

@ -55,6 +55,7 @@ public abstract class HelpModuleLocation {
public abstract boolean isHelpInputSource();
protected void loadHelpTopics() {
Path helpTopicsDir = helpDir.resolve("topics");
if (!Files.exists(helpTopicsDir)) {
HelpBuildUtils.debug("No topics found in help dir: " + this);

View File

@ -272,6 +272,9 @@ tasks.register('indexHelp', JavaExec) {
// tell the indexer where send its output
args '-db', outputFile.absolutePath
// debug
// args '-verbose'
// for each help file that was found, add it as an argument to the indexer
helpFiles.each { File file ->
args "${file.absolutePath}"
@ -330,8 +333,11 @@ tasks.register('buildHelpFiles', JavaExec) {
args '-o', "${outputDir.absolutePath}" // set the output directory arg
// to allow remote debugging of the help build jvm
// jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=13001'
// args '-debug' // print debug info
// print debug info
// args '-debug'
doFirst {
@ -393,7 +399,7 @@ tasks.register('buildHelp', Jar) {
dependsOn tasks.named('buildHelpFiles')
duplicatesStrategy 'exclude'
from "build/help/main" // include the generated help and index files from
from "build/help/main" // include the generated help and index files
from "src/main/help" // include the help source files
destinationDirectory = file("build/libs")