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> <HTML>
<HEAD>
<TITLE>Ghidra User Agreement</TITLE>
</HEAD>
<FONT SIZE="5"> <FONT SIZE="5">

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -66,7 +66,7 @@ def installPoint = "../help/help"
/** /**
* Build the pdf docs for BSim and place into the '$installPoint' directory. * 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. * There is an associated, auto-generated clean task.
**/ **/
task buildBSimHelpPdf(type: Exec) { task buildBSimHelpPdf(type: Exec) {
@ -136,7 +136,7 @@ task buildBSimHelpPdf(type: Exec) {
/** /**
* Build the html docs for BSim and place into the '$installPoint' directory. * 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) { task buildBSimHelpHtml(type: Exec) {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -140,11 +140,19 @@ tasks.register('generateExtraHelpFiles') {
} }
} }
// Base's help includes the file generated by the 'generateExtraHelpFiles' task. Signal that we tasks.named('buildHelpFiles') {
// depend on that task and it's output file.
tasks.named('buildHelp') {
dependsOn(tasks.named('generateExtraHelpFiles')) dependsOn(tasks.named('generateExtraHelpFiles'))
inputs.files tasks.named('generateExtraHelpFiles').get().outputs 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) { 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="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="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="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="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="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" /> <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). 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. 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 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. 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 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. 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. This is the last tip. You can turn them off now.

View File

@ -44,4 +44,4 @@ rootProject.assembleDistribution {
include "src/pdb/**" include "src/pdb/**"
into { getZipPath(this.project) } into { getZipPath(this.project) }
} }
} }

View File

@ -52,7 +52,7 @@
<tocref id="Program Annotation"> <tocref id="Program Annotation">
<tocdef id="PDB" sortgroup="q" text="PDB" target="help/topics/Pdb/PDB.htm" > <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="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> </tocdef>
</tocref> </tocref>
</tocroot> </tocroot>

View File

@ -102,7 +102,7 @@
</UL></BLOCKQUOTE> </UL></BLOCKQUOTE>
</P> </P>
<P><B>NOTE:</B> Execution of <i>pdb.exe</i> has runtime dependencies which must be satisfied. <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> <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 <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; 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 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> </P>
</BLOCKQUOTE> </BLOCKQUOTE>
<P class="relatedtopic">Related Topics:</P> <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. * Returns a collection of module library directories. Library directories are optional for a module.
* @return a collection of module library directories. * @return a collection of module library directories.
* @see ModuleUtilities#getModuleLibDirectories(Collection)
*/ */
public static Collection<ResourceFile> getLibraryDirectories() { public static Collection<ResourceFile> getLibraryDirectories() {
checkAppInitialized(); checkAppInitialized();

View File

@ -51,12 +51,14 @@ public class GHelpBuilder {
private static final String OUTPUT_DIRECTORY_OPTION = "-o"; private static final String OUTPUT_DIRECTORY_OPTION = "-o";
private static final String MODULE_NAME_OPTION = "-n"; private static final String MODULE_NAME_OPTION = "-n";
private static final String HELP_PATHS_OPTION = "-hp"; 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 DEBUG_SWITCH = "-debug";
private static final String IGNORE_INVALID_SWITCH = "-ignoreinvalid"; private static final String IGNORE_INVALID_SWITCH = "-ignoreinvalid";
private String outputDirectoryName; private String outputDirectoryName;
private String moduleName; private String moduleName;
private Collection<File> dependencyHelpPaths = new LinkedHashSet<>(); private Collection<File> dependencyHelpPaths = new LinkedHashSet<>();
private Collection<File> generatedDependencyHelpPaths = new LinkedHashSet<>();
private Collection<File> helpInputDirectories = new LinkedHashSet<>(); private Collection<File> helpInputDirectories = new LinkedHashSet<>();
private static boolean debugEnabled = false; private static boolean debugEnabled = false;
private boolean ignoreInvalid = false; // TODO: Do actual validation here private boolean ignoreInvalid = false; // TODO: Do actual validation here
@ -112,7 +114,22 @@ public class GHelpBuilder {
for (File file : dependencyHelpPaths) { for (File file : dependencyHelpPaths) {
allHelp.add(file); 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) { private Results validateHelpDirectories(HelpModuleCollection help, LinkDatabase linkDatabase) {
@ -163,11 +180,11 @@ public class GHelpBuilder {
JavaHelpFilesBuilder fileBuilder = JavaHelpFilesBuilder fileBuilder =
new JavaHelpFilesBuilder(outputDirectory, moduleName, linkDatabase); 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) // 1) Generate JavaHelp files for the module (e.g., TOC file, map file)
try { try {
fileBuilder.generateHelpFiles(help); fileBuilder.generateHelpFiles(helpInput);
} }
catch (Exception e) { catch (Exception e) {
exitWithError("Unexpected error building help module files:\n", 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)) { else if (opt.equals(DEBUG_SWITCH)) {
debugEnabled = true; debugEnabled = true;
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -21,7 +21,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.util.*; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -36,7 +37,6 @@ import javax.swing.text.html.HTML.Tag;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import generic.theme.GColor; import generic.theme.GColor;
import generic.theme.Gui; import generic.theme.Gui;
import ghidra.framework.Application;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg; import ghidra.util.Msg;
import resources.*; import resources.*;
@ -183,6 +183,10 @@ public class GHelpHTMLEditorKit extends HTMLEditorKit {
*/ */
private HyperlinkEvent validateURL(HyperlinkEvent event) { private HyperlinkEvent validateURL(HyperlinkEvent event) {
URL url = event.getURL(); URL url = event.getURL();
if (url == null) {
Msg.trace(this, "No URL for link: " + event);
return maybeCreateNewHyperlinkEventWithUpdatedURL(event);
}
try { try {
url.openStream();// assume that this will fail if the file does not exist 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 // directory). See if it may be a relative link to a build's installation root (like
// a file in <install dir>/docs). // a file in <install dir>/docs).
// //
newUrl = findApplicationfile(HREF); newUrl = HelpBuildUtils.findApplicationUrl(HREF);
return newUrl; return newUrl;
} }
@ -385,37 +389,15 @@ public class GHelpHTMLEditorKit extends HTMLEditorKit {
return url; return url;
} }
return findModuleFile("help/shared/" + name); ResourceFile file = HelpBuildUtils.findModuleFile("help/shared/" + name);
} if (file != null) {
private URL findApplicationfile(String relativePath) {
ResourceFile installDir = Application.getInstallationDirectory();
ResourceFile file = new ResourceFile(installDir, relativePath);
if (file.exists()) {
try { try {
return file.toURL(); return file.toURL();
} }
catch (MalformedURLException e) { catch (MalformedURLException e) {
Msg.showError(this, null, "Unexpected Error", Msg.showError(this, null, "Unexpected Error",
"Unexpected error parsing file to URL: " + file); "Unexpected error parsing file to URL: " + file);
} return null;
}
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

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -28,9 +28,8 @@ import javax.help.*;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import help.validator.JavaHelpValidator;
/** /**
* Ghidra help set that creates a GhidraHelpBroker, installs some custom HTML handling code via * 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 * 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. * 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. * @return the URL to the help file.
*/ */
private URL tryToCreateURLFromID(String id) { private URL tryToCreateURLFromID(String rawId) {
URL fileURL = createFileURL(id); String idText = rawId;
if (fileURL != null) { if (rawId.startsWith(JavaHelpValidator.EXTERNAL_PREFIX)) {
return fileURL; 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; return rawURL;
} }
@ -238,31 +242,6 @@ public class GHelpSet extends HelpSet {
return null; 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 @Override
public boolean isID(URL url) { public boolean isID(URL url) {
return mapDelegate.isID(url); return mapDelegate.isID(url);

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -30,7 +30,8 @@ import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors;
import generic.theme.Gui; import generic.theme.Gui;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.util.HelpLocation; import ghidra.util.*;
import help.validator.JavaHelpValidator;
import help.validator.location.*; import help.validator.location.*;
import resources.IconProvider; import resources.IconProvider;
import resources.Icons; import resources.Icons;
@ -166,6 +167,71 @@ public class HelpBuildUtils {
return null; 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 // Cleanup Methods
//================================================================================================== //==================================================================================================

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -220,6 +220,7 @@ public class JavaHelpFilesBuilder {
Files.delete(file); Files.delete(file);
} }
catch (IOException e) { catch (IOException e) {
// ignore
} }
} }
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -29,23 +29,13 @@ import help.validator.location.HelpModuleCollection;
import help.validator.model.*; import help.validator.model.*;
public class JavaHelpValidator { 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; 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 String moduleName;
private HelpModuleCollection help; private HelpModuleCollection help;
@ -304,7 +294,7 @@ public class JavaHelpValidator {
path = path.substring(0, index); path = path.substring(0, index);
} }
return EXCLUDED_FILE_NAMES.contains(path); return path.startsWith(EXTERNAL_PREFIX);
} }
private void validateExternalFileLinks(LinkDatabase linkDatabase) { private void validateExternalFileLinks(LinkDatabase linkDatabase) {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,6 +15,12 @@
*/ */
package help.validator; 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.OverlayHelpTree;
import help.TOCItemProvider; import help.TOCItemProvider;
import help.validator.links.InvalidHREFLink; import help.validator.links.InvalidHREFLink;
@ -22,12 +28,6 @@ import help.validator.links.InvalidLink;
import help.validator.location.HelpModuleCollection; import help.validator.location.HelpModuleCollection;
import help.validator.model.*; 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 { public class LinkDatabase {
/** Sorted for later presentation */ /** 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

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,21 +16,10 @@
package help.validator.location; package help.validator.location;
import java.io.File; import java.io.File;
import java.net.MalformedURLException; import java.net.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.*;
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.stream.Collectors; import java.util.stream.Collectors;
import javax.help.HelpSet; import javax.help.HelpSet;
@ -38,19 +27,9 @@ import javax.help.Map.ID;
import javax.help.TOCView; import javax.help.TOCView;
import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultMutableTreeNode;
import help.*;
import help.CustomTOCView.CustomTreeItemDecorator; import help.CustomTOCView.CustomTreeItemDecorator;
import help.HelpBuildUtils; import help.validator.model.*;
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;
/** /**
* A class that is meant to hold a single help <b>input</b> directory and 0 or more * 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) { if (inputHelp == null && externalHelpSets.size() == 0) {
throw new IllegalArgumentException( 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. " + "even if it is an empty template, or provide a pre-built TOC. " +
"Help directories: " + locations.toString()); "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() { public GhidraTOCFile getSourceTOCFile() {
return inputHelp.getSourceTOCFile(); return inputHelp.getSourceTOCFile();
} }
@ -160,17 +148,17 @@ public class HelpModuleCollection implements TOCItemProvider {
externalHelpSets = new ArrayList<>(); externalHelpSets = new ArrayList<>();
for (HelpModuleLocation location : helpLocations) { for (HelpModuleLocation location : helpLocations) {
if (location.isHelpInputSource()) { doAddHelpSet(location);
continue; // help sets only exist in pre-built help }
} }
HelpSet helpSet = location.getHelpSet(); private void doAddHelpSet(HelpModuleLocation location) {
externalHelpSets.add(helpSet); if (location.isHelpInputSource()) {
return; // help sets only exist in pre-built help
} }
if (externalHelpSets.isEmpty()) { HelpSet helpSet = location.getHelpSet();
return; externalHelpSets.add(helpSet);
}
} }
public boolean containsHelpFiles() { public boolean containsHelpFiles() {
@ -230,7 +218,8 @@ public class HelpModuleCollection implements TOCItemProvider {
public Collection<AnchorDefinition> getAllAnchorDefinitions() { public Collection<AnchorDefinition> getAllAnchorDefinitions() {
List<AnchorDefinition> result = new ArrayList<>(); List<AnchorDefinition> result = new ArrayList<>();
for (HelpModuleLocation location : helpLocations) { for (HelpModuleLocation location : helpLocations) {
result.addAll(location.getAllAnchorDefinitions()); Collection<AnchorDefinition> anchors = location.getAllAnchorDefinitions();
result.addAll(anchors);
} }
return result; return result;
} }
@ -250,7 +239,6 @@ public class HelpModuleCollection implements TOCItemProvider {
if (helpPath == null) { if (helpPath == null) {
return null; return null;
} }
Map<PathKey, HelpFile> map = getPathHelpFileMap(); Map<PathKey, HelpFile> map = getPathHelpFileMap();
return map.get(new PathKey(helpPath)); return map.get(new PathKey(helpPath));
} }
@ -373,4 +361,5 @@ public class HelpModuleCollection implements TOCItemProvider {
public String toString() { public String toString() {
return helpLocations.toString(); return helpLocations.toString();
} }
} }

View File

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

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -272,6 +272,9 @@ tasks.register('indexHelp', JavaExec) {
// tell the indexer where send its output // tell the indexer where send its output
args '-db', outputFile.absolutePath args '-db', outputFile.absolutePath
// debug
// args '-verbose'
// for each help file that was found, add it as an argument to the indexer // for each help file that was found, add it as an argument to the indexer
helpFiles.each { File file -> helpFiles.each { File file ->
args "${file.absolutePath}" args "${file.absolutePath}"
@ -321,7 +324,7 @@ tasks.register('buildHelpFiles', JavaExec) {
// configuration time. // configuration time.
return getHelpInputs(sourceSets.main.runtimeClasspath.files) return getHelpInputs(sourceSets.main.runtimeClasspath.files)
}) })
outputs.dir outputDir outputs.dir outputDir
mainClass = 'help.GHelpBuilder' mainClass = 'help.GHelpBuilder'
@ -330,8 +333,11 @@ tasks.register('buildHelpFiles', JavaExec) {
args '-o', "${outputDir.absolutePath}" // set the output directory arg 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 { doFirst {
@ -368,7 +374,7 @@ tasks.register('buildHelpFiles', JavaExec) {
args "${helpJar.absolutePath}" args "${helpJar.absolutePath}"
} }
} }
// The help dir to process. This needs to be the last argument to the process, // 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 // thus, this is why it is inside of this block
args "${helpRootDir.absolutePath}" args "${helpRootDir.absolutePath}"
@ -393,7 +399,7 @@ tasks.register('buildHelp', Jar) {
dependsOn tasks.named('buildHelpFiles') dependsOn tasks.named('buildHelpFiles')
duplicatesStrategy 'exclude' 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 from "src/main/help" // include the help source files
destinationDirectory = file("build/libs") destinationDirectory = file("build/libs")