GP-4564: Improvements to library search paths and other loader options

This commit is contained in:
Ryan Kurtz 2024-06-05 08:40:08 -04:00
parent 4302fdc00d
commit d3d60ea399
19 changed files with 466 additions and 376 deletions

View File

@ -89,7 +89,7 @@ public class ScriptPathsPropertyEditor extends AbstractTypedPropertyEditor<Strin
protected class ScriptPathsPanel extends PathnameTablePanel {
public ScriptPathsPanel(String[] paths, Callback resetCallback) {
// disable edits, top/bottom irrelevant, unordered
super(paths, resetCallback, false, false, false);
super(paths, resetCallback, false, false, false, false);
}
@Override

View File

@ -62,6 +62,7 @@ The Headless Analyzer can be useful when performing repetitive tasks on a projec
<BR>&nbsp;&nbsp;&nbsp;&nbsp;[-p]
<BR>&nbsp;&nbsp;&nbsp;&nbsp;[-commit [&quot;&lt;comment&gt;&quot;]]
<BR>&nbsp;&nbsp;&nbsp;&nbsp;[-max-cpu &lt;max cpu cores to use&gt;]
<BR>&nbsp;&nbsp;&nbsp;&nbsp;[-librarySearchPaths &lt;path1&gt;[;&lt;path2&gt;...]]
<BR>&nbsp;&nbsp;&nbsp;&nbsp;[-loader &lt;desired loader name&gt;]
</FONT>
</P>

View File

@ -311,18 +311,8 @@
<P>The project folder that will get searched for existing library programs. If left
empty, the folder that the main program is being imported to will be searched.</P>
</BLOCKQUOTE>
<H4>Load Local Libraries From Disk</H4>
<BLOCKQUOTE>
<P>Searches the executable's directory to recursively resolve the external libraries used
by the executable. The entire library dependency tree will be traversed in a depth-first
manner and a program will be created for each found library (if it doesn't exist already).
The <A href="help/topics/ReferencesPlugin/References_from.htm#extRefs">external references</A>
in these programs will be resolved.<BR>
</BLOCKQUOTE>
<H4>Load System Libraries From Disk</H4>
<H4>Load Libraries From Disk</H4>
<BLOCKQUOTE>
<P>Searches a user-defined path list to recursively resolve the external libraries used
@ -696,8 +686,12 @@
<H2><A name="Library_Paths"></A>Library Search Path</H2>
<BLOCKQUOTE>
<P>The Library Search Path dialog is used to specify the directories that Ghidra should use
to resolve external libraries (e.g.; *.dll, *.so) while importing.</P>
<P>The Library Search Path dialog is used to specify the directories, container files,
and/or FSRLs that Ghidra should use to resolve external libraries (e.g.; *.dll, *.so) while
importing. A "." can be added to specify the the program's import location. FSRLs can be
added via the
<A href="help/topics/FileSystemBrowserPlugin/FileSystemBrowserPlugin.html#FSB_Add_Library_Search_Path">File System Browser context menu</A>.
</P>
</BLOCKQUOTE>
<P align="center"><IMG alt="" src="images/SearchPathsDialog.png"></P>
@ -734,7 +728,8 @@
<OL>
<LI>Click the <IMG alt="" src="images/Plus.png"> button</LI>
<LI>Select a directory from the file chooser</LI>
<LI>Select a directory or container file from the file chooser, or the program's
import location if "." is not present in the list already</LI>
<LI>Click the "Select Directory" button</LI>
</OL>

View File

@ -17,6 +17,13 @@ package ghidra.app.util;
import java.awt.Component;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.Project;
import ghidra.framework.options.SaveState;
import ghidra.program.model.address.*;
import ghidra.util.NumericUtilities;
@ -28,6 +35,7 @@ public class Option {
private final String name;
private final Class<?> valueClass;
private final String commandLineArgument;
private final String stateKey;
private Object value;
private OptionListener listener;
@ -85,11 +93,27 @@ public class Option {
* @param group Name for group of options
*/
public Option(String name, Class<?> valueClass, Object value, String arg, String group) {
this(name, valueClass, value, arg, group, null);
}
/**
* Construct a new Option
*
* @param name name of the option
* @param valueClass class of the option's value
* @param value value of the option
* @param arg the option's command line argument
* @param group Name for group of options
* @param stateKey state key name
*/
public Option(String name, Class<?> valueClass, Object value, String arg, String group,
String stateKey) {
this.name = name;
this.valueClass = valueClass;
this.commandLineArgument = arg;
this.group = group;
this.value = value;
this.stateKey = stateKey;
}
public void setOptionListener(OptionListener listener) {
@ -110,29 +134,28 @@ public class Option {
}
/**
* Return the class of the value for this Option.
* {@return the class of the value for this option}
*/
public Class<?> getValueClass() {
return valueClass;
}
/**
* Return the group name for this option; may be null if group was
* not specified.
* {@return the group name for this option; may be null if group was not specified}
*/
public String getGroup() {
return group;
}
/**
* Return the name of this Option.
* {@return the name of this option}
*/
public String getName() {
return name;
}
/**
* Return the value of this Option.
* {@return the value of this option}
*/
public Object getValue() {
return value;
@ -168,19 +191,12 @@ public class Option {
return false;
}
if (Boolean.class.isAssignableFrom(getValueClass())) {
Boolean b = null;
if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("t") ||
str.equalsIgnoreCase("yes") || str.equalsIgnoreCase("y")) {
b = true;
try {
setValue(BooleanUtils.toBoolean(str, "true", "false"));
}
else if (str.equalsIgnoreCase("false") || str.equalsIgnoreCase("f") ||
str.equalsIgnoreCase("no") || str.equalsIgnoreCase("n")) {
b = false;
}
if (b == null) {
catch (IllegalArgumentException e) {
return false;
}
setValue(b);
}
else if (HexLong.class.isAssignableFrom(getValueClass())) {
try {
@ -231,18 +247,45 @@ public class Option {
}
/**
* Return the command line argument for this Option.
*
* @return The command line argument for this Option. Could be null.
* {@return the command line argument for this option (could be null)}
*/
public String getArg() {
return commandLineArgument;
}
/**
* {@return the state key name (could be null)}
*/
public String getStateKey() {
return stateKey;
}
/**
* {@return the current project state associated with this option (could be null)}
*/
public SaveState getState() {
Project project = AppInfo.getActiveProject();
if (project == null) {
return null;
}
final SaveState state;
SaveState existingState = stateKey != null ? project.getSaveableData(stateKey) : null;
if (existingState != null) {
state = existingState;
}
else if (stateKey != null) {
state = new SaveState();
project.setSaveableData(stateKey, state);
}
else {
state = null;
}
return state;
}
@Override
public String toString() {
return "Group:\"" + group + "\" Name:\"" + name + "\" Arg:\"" + commandLineArgument +
"\" Type:\"" + valueClass + "\" Value:\"" + value + "\"";
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
/**
@ -250,7 +293,7 @@ public class Option {
* @return a copy of this Option object.
*/
public Option copy() {
return new Option(name, valueClass, value, commandLineArgument, group);
return new Option(name, valueClass, value, commandLineArgument, group, stateKey);
}
private static Class<?> getValueClass(Object v) {

View File

@ -15,8 +15,6 @@
*/
package ghidra.app.util;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.*;
import java.util.*;
import java.util.List;
@ -28,17 +26,10 @@ import javax.swing.event.DocumentListener;
import org.apache.commons.collections4.map.LazyMap;
import docking.DockingWindowManager;
import docking.widgets.button.BrowseButton;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GLabel;
import docking.widgets.textfield.IntegerTextField;
import ghidra.app.util.opinion.*;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.Project;
import ghidra.framework.options.SaveState;
import ghidra.program.model.address.*;
import ghidra.util.exception.AssertException;
@ -200,15 +191,6 @@ public class OptionsEditorPanel extends JPanel {
*/
private Component getEditorComponent(Option option) {
// Special cases for library link/load options
if (option.getName().equals(AbstractLibrarySupportLoader.LINK_SEARCH_FOLDER_OPTION_NAME) ||
option.getName().equals(AbstractLibrarySupportLoader.LIBRARY_DEST_FOLDER_OPTION_NAME)) {
return buildProjectFolderEditor(option);
}
if (option.getName().equals(AbstractLibrarySupportLoader.SYSTEM_LIBRARY_OPTION_NAME)) {
return buildPathsEditor(option);
}
Component customEditorComponent = option.getCustomEditorComponent();
if (customEditorComponent != null) {
return customEditorComponent;
@ -243,59 +225,6 @@ public class OptionsEditorPanel extends JPanel {
}
}
private Component buildPathsEditor(Option option) {
JPanel panel = new JPanel(new BorderLayout());
JButton button = new JButton("Edit Paths");
button.addActionListener(
e -> DockingWindowManager.showDialog(panel, new LibraryPathsDialog()));
Boolean value = (Boolean) option.getValue();
boolean initialState = value != null ? value : false;
GCheckBox jCheckBox = new GCheckBox("", initialState);
jCheckBox.addActionListener(e -> {
boolean b = jCheckBox.isSelected();
option.setValue(b);
});
panel.add(jCheckBox, BorderLayout.WEST);
panel.add(button, BorderLayout.EAST);
return panel;
}
private Component buildProjectFolderEditor(Option option) {
Project project = AppInfo.getActiveProject();
final SaveState state;
SaveState existingState = project.getSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY);
if (existingState != null) {
state = existingState;
}
else {
state = new SaveState();
project.setSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY, state);
}
String lastFolderPath = state.getString(option.getName(), "");
option.setValue(lastFolderPath);
JTextField textField = new JTextField(lastFolderPath);
textField.setEditable(false);
JButton button = new BrowseButton();
button.addActionListener(e -> {
DataTreeDialog dataTreeDialog =
new DataTreeDialog(this, "Choose a project folder", CHOOSE_FOLDER);
String folderPath = lastFolderPath.isBlank() ? "/" : lastFolderPath;
dataTreeDialog.setSelectedFolder(project.getProjectData().getFolder(folderPath));
dataTreeDialog.showComponent();
DomainFolder folder = dataTreeDialog.getDomainFolder();
if (folder != null) {
String newFolderPath = folder.getPathname();
textField.setText(newFolderPath);
option.setValue(newFolderPath);
state.putString(option.getName(), newFolderPath);
}
});
JPanel panel = new JPanel(new BorderLayout());
panel.add(textField, BorderLayout.CENTER);
panel.add(button, BorderLayout.EAST);
return panel;
}
private Component getAddressSpaceEditorComponent(Option option) {
if (addressFactoryService == null) {
return null;
@ -320,49 +249,84 @@ public class OptionsEditorPanel extends JPanel {
}
private Component getStringEditorComponent(Option option) {
final SaveState state = option.getState();
String defaultValue = (String) option.getValue();
String value =
state != null ? state.getString(option.getName(), defaultValue) : defaultValue;
option.setValue(value);
JTextField tf = new JTextField(5);
tf.setName(option.getName());
tf.getDocument().addDocumentListener(new ImporterDocumentListener(option, tf));
String value = option.getValue() == null ? "" : (String) option.getValue();
tf.getDocument().addDocumentListener(new ImporterDocumentListener(option, tf, state));
tf.setText(value);
return tf;
}
private Component getHexLongEditorComponent(Option option) {
final SaveState state = option.getState();
HexLong defaultValue = (HexLong) option.getValue();
long value = state != null ? state.getLong(option.getName(), defaultValue.longValue())
: defaultValue.longValue();
option.setValue(new HexLong(value));
IntegerTextField field = new IntegerTextField();
HexLong hexLong = (HexLong) option.getValue();
long value = hexLong == null ? 0 : hexLong.longValue();
field.setValue(value);
field.setHexMode();
field.addChangeListener(e -> option.setValue(new HexLong(field.getLongValue())));
field.addChangeListener(e -> {
option.setValue(new HexLong(field.getLongValue()));
if (state != null) {
state.putLong(option.getName(), field.getLongValue());
}
});
return field.getComponent();
}
private Component getIntegerEditorComponent(Option option) {
final SaveState state = option.getState();
int defaultValue = (int) option.getValue();
int value = state != null ? state.getInt(option.getName(), defaultValue) : defaultValue;
option.setValue(value);
IntegerTextField field = new IntegerTextField();
Integer value = (Integer) option.getValue();
if (value != null) {
field.setValue(value);
}
field.addChangeListener(e -> option.setValue(field.getIntValue()));
field.setValue(value);
field.addChangeListener(e -> {
option.setValue(field.getIntValue());
if (state != null) {
state.putInt(option.getName(), field.getIntValue());
}
});
return field.getComponent();
}
private Component getLongEditorComponent(Option option) {
final SaveState state = option.getState();
long defaultValue = (long) option.getValue();
long value =
state != null ? state.getLong(option.getName(), defaultValue) : defaultValue;
option.setValue(value);
IntegerTextField field = new IntegerTextField();
Long value = (Long) option.getValue();
field.setValue(value);
field.addChangeListener(e -> option.setValue(field.getLongValue()));
field.addChangeListener(e -> {
option.setValue(field.getLongValue());
if (state != null) {
state.putLong(option.getName(), field.getLongValue());
}
});
return field.getComponent();
}
private Component getBooleanEditorComponent(Option option) {
final SaveState state = option.getState();
boolean defaultValue = (boolean) option.getValue();
boolean initialState =
state != null ? state.getBoolean(option.getName(), defaultValue) : defaultValue;
option.setValue(initialState);
GCheckBox cb = new GCheckBox();
cb.setName(option.getName());
Boolean b = (Boolean) option.getValue();
boolean initialState = b != null ? b : false;
cb.setSelected(initialState);
cb.addItemListener(e -> option.setValue(cb.isSelected()));
cb.addItemListener(e -> {
option.setValue(cb.isSelected());
if (state != null) {
state.putBoolean(option.getName(), cb.isSelected());
}
});
return cb;
}
@ -383,16 +347,17 @@ public class OptionsEditorPanel extends JPanel {
addressInput.addChangeListener(e -> option.setValue(addressInput.getAddress()));// addressInput.addActionListener(e -> option.setValue(addressInput.getAddress()));
return addressInput;
}
}
class ImporterDocumentListener implements DocumentListener {
private Option option;
private JTextField textField;
private SaveState state;
ImporterDocumentListener(Option option, JTextField textField) {
ImporterDocumentListener(Option option, JTextField textField, SaveState state) {
this.option = option;
this.textField = textField;
this.state = state;
}
@Override
@ -413,5 +378,8 @@ class ImporterDocumentListener implements DocumentListener {
private void updated() {
String text = textField.getText();
option.setValue(text);
if (state != null) {
state.putString(option.getName(), text);
}
}
}

View File

@ -23,6 +23,7 @@ import java.util.*;
import generic.stl.Pair;
import ghidra.*;
import ghidra.app.util.importer.LibrarySearchPathManager;
import ghidra.app.util.opinion.Loader;
import ghidra.framework.*;
import ghidra.framework.model.DomainFolder;
@ -341,6 +342,9 @@ public class AnalyzeHeadless implements GhidraLaunchable {
else if ("-okToDelete".equalsIgnoreCase(args[argi])) {
options.setOkToDelete(true);
}
else if (checkArgument("-librarySearchPaths", args, argi)) {
LibrarySearchPathManager.setLibraryPaths(args[++argi].split(";"));
}
else {
throw new InvalidInputException("Bad argument: " + arg);
}

View File

@ -17,8 +17,7 @@ package ghidra.app.util.importer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.function.Predicate;
import generic.stl.Pair;
@ -718,6 +717,8 @@ public final class AutoImporter {
Msg.info(AutoImporter.class, "Using Loader: " + loadSpec.getLoader().getName());
Msg.info(AutoImporter.class,
"Using Language/Compiler: " + loadSpec.getLanguageCompilerSpec());
Msg.info(AutoImporter.class, "Using Library Search Path: " +
Arrays.toString(LibrarySearchPathManager.getLibraryPaths()));
LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
.load(provider, importName, project, projectFolderPath, loadSpec, loaderOptions,
messageLog, consumer, monitor);

View File

@ -0,0 +1,90 @@
/* ###
* 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 ghidra.app.util.importer;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.*;
import docking.widgets.button.BrowseButton;
import ghidra.app.util.Option;
import ghidra.app.util.opinion.Loader;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.options.SaveState;
/**
* An {@link Option} used to specify a {@link DomainFolder}
*/
public class DomainFolderOption extends Option {
/**
* Creates a new {@link DomainFolderOption}
*
* @param name The name of the option
* @param arg The option's command line argument (could be null)
*/
public DomainFolderOption(String name, String arg) {
super(name, String.class, "", arg, null, Loader.OPTIONS_PROJECT_SAVE_STATE_KEY);
}
@Override
public Component getCustomEditorComponent() {
final SaveState state = getState();
String defaultValue = (String) getValue();
String lastFolderPath =
state != null ? state.getString(getName(), defaultValue) : defaultValue;
setValue(lastFolderPath);
JTextField textField = new JTextField(lastFolderPath);
textField.setEditable(false);
JButton button = new BrowseButton();
button.addActionListener(e -> {
DataTreeDialog dataTreeDialog =
new DataTreeDialog(null, "Choose a project folder", CHOOSE_FOLDER);
String folderPath = lastFolderPath.isBlank() ? "/" : lastFolderPath;
dataTreeDialog.setSelectedFolder(
AppInfo.getActiveProject().getProjectData().getFolder(folderPath));
dataTreeDialog.showComponent();
DomainFolder folder = dataTreeDialog.getDomainFolder();
if (folder != null) {
String newFolderPath = folder.getPathname();
textField.setText(newFolderPath);
setValue(newFolderPath);
if (state != null) {
state.putString(getName(), newFolderPath);
}
}
});
JPanel panel = new JPanel(new BorderLayout());
panel.add(textField, BorderLayout.CENTER);
panel.add(button, BorderLayout.EAST);
return panel;
}
@Override
public Class<?> getValueClass() {
return String.class;
}
@Override
public Option copy() {
return new DomainFolderOption(getName(), getArg());
}
}

View File

@ -0,0 +1,59 @@
/* ###
* 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 ghidra.app.util.importer;
import java.awt.Component;
import javax.swing.JButton;
import docking.DockingWindowManager;
import ghidra.app.util.Option;
import ghidra.app.util.opinion.LibraryPathsDialog;
/**
* A dummy {@link Option} used to render a button that will allow the user to edit the global
* list of library search paths
*/
public class LibrarySearchPathDummyOption extends Option {
/**
* Creates a new {@link LibrarySearchPathDummyOption}
*
* @param name The name of the option
*/
public LibrarySearchPathDummyOption(String name) {
super(name, null);
}
@Override
public Component getCustomEditorComponent() {
JButton button = new JButton("Edit Paths");
button.addActionListener(e -> {
DockingWindowManager.showDialog(null, new LibraryPathsDialog());
});
return button;
}
@Override
public Class<?> getValueClass() {
return Object.class;
}
@Override
public Option copy() {
return new LibrarySearchPathDummyOption(getName());
}
}

View File

@ -20,70 +20,65 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.util.*;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.opinion.Loader;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.Platform;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.Project;
import ghidra.framework.options.SaveState;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A simple class for managing the library search path
* and avoiding duplicate directories.
* A simple class for managing the library search path and avoiding duplicate directories.
*/
public class LibrarySearchPathManager {
private static List<String> pathList = createPathList();
private static boolean hasBeenRestored;
private static List<String> createPathList() {
pathList = new ArrayList<>();
loadJavaLibraryPath();
return pathList;
}
private static void loadJavaLibraryPath() {
List<String> paths = Platform.CURRENT_PLATFORM.getAdditionalLibraryPaths();
for (String path : paths) {
addPath(path);
}
String libpath = System.getProperty("java.library.path");
String libpathSep = System.getProperty("path.separator");
StringTokenizer nizer = new StringTokenizer(libpath, libpathSep);
while (nizer.hasMoreTokens()) {
String path = nizer.nextToken();
addPath(path);
}
}
private static final String LIBRARY_SEARCH_PATH_STATE_NAME = "Library Search Paths";
private static Set<String> pathSet = initialize();
/**
* Returns an array of directories to search for libraries
* @return an array of directories to search for libraries
* Returns an array of library search paths
*
* @return an array of library search paths
*/
public static String[] getLibraryPaths() {
String[] paths = new String[pathList.size()];
pathList.toArray(paths);
public static synchronized String[] getLibraryPaths() {
String[] paths = new String[pathSet.size()];
pathSet.toArray(paths);
return paths;
}
/**
* Returns a {@link List} of {@link FSRL}s to search for libraries
*
* @param provider The {@link ByteProvider} of the program being loaded
* @param log The log
* @param monitor A cancellable monitor
* @return a {@link List} of {@link FSRL}s to search for libraries
* @throws CancelledException if the user cancelled the operation
*/
public static List<FSRL> getLibraryFsrlList(MessageLog log, TaskMonitor monitor)
throws CancelledException {
public static synchronized List<FSRL> getLibraryFsrlList(ByteProvider provider, MessageLog log,
TaskMonitor monitor) throws CancelledException {
FileSystemService fsService = FileSystemService.getInstance();
List<FSRL> fsrlList = new ArrayList<>();
for (String path : pathList) {
for (String path : pathSet) {
monitor.checkCancelled();
path = path.trim();
FSRL fsrl = null;
try {
fsrl = FSRL.fromString(path);
if (path.equals(".")) {
FSRL providerFsrl = provider.getFSRL();
if (providerFsrl != null) {
try (RefdFile fileRef = fsService.getRefdFile(providerFsrl, monitor)) {
GFile parentFile = fileRef.file.getParentFile();
fsrl = parentFile.getFSRL();
}
}
}
else {
fsrl = FSRL.fromString(path);
}
}
catch (MalformedURLException e) {
try {
@ -96,6 +91,9 @@ public class LibrarySearchPathManager {
log.appendException(e2);
}
}
catch (IOException e) {
log.appendException(e);
}
if (fsrl != null) {
fsrlList.add(fsrl);
}
@ -104,85 +102,95 @@ public class LibrarySearchPathManager {
}
/**
* Sets the directories to search for libraries
* Sets the library search paths to the given array
*
* @param paths the new library search paths
*/
public static void setLibraryPaths(String[] paths) {
pathList.clear();
for (String path : paths) {
addPath(path);
}
public static synchronized void setLibraryPaths(String[] paths) {
pathSet.clear();
pathSet.addAll(Arrays.asList(paths));
saveState();
}
/**
* Call this to restore paths that were previously persisted. If you really need to change
* the paths <b>for the entire JVM</b>, then call {@link #setLibraryPaths(String[])}.
*
* @param paths the paths to restore
*/
public static void restoreLibraryPaths(String[] paths) {
if (hasBeenRestored) {
//
// We code that restores paths from tool config files. It is a mistake to do this
// every time we load a tool, as the values can get out-of-sync if tools do not
// save properly. Logically, we only need to restore once.
//
return;
}
setLibraryPaths(paths);
}
/**
* Adds the specified path to the end of the path search list.
* @param path the path to add
* Adds the specified library search path path to the end of the path search list
*
* @param path the library search path to add
* @return true if the path was appended, false if the path was a duplicate
*/
public static boolean addPath(String path) {
if (pathList.indexOf(path) == -1) {
pathList.add(path);
return true;
public static synchronized boolean addPath(String path) {
if (pathSet.contains(path)) {
return false;
}
return false;
pathSet.add(path);
saveState();
return true;
}
/**
* Adds the path at the specified index in path search list.
* @param index The index
* @param path the path to add
* @return true if the path was appended, false if the path was a duplicate
* Resets the library search path to the default values
*/
public static boolean addPathAt(int index, String path) {
if (pathList.indexOf(path) == -1) {
pathList.add(index, path);
return true;
public static synchronized void reset() {
pathSet = loadDefaultPaths();
saveState();
}
private LibrarySearchPathManager() {
// Prevent instantiation of utility class
}
private static synchronized Set<String> initialize() {
Set<String> set = loadFromSavedState();
if (set == null) {
set = loadDefaultPaths();
}
return false;
return set;
}
/**
* Removes the path from the path search list.
* @param path the path the remove
* @return true if the path was removed, false if the path did not exist
*/
public static boolean removePath(String path) {
return pathList.remove(path);
private static synchronized Set<String> loadDefaultPaths() {
Set<String> set = new LinkedHashSet<>();
// Add program import location
set.add(".");
// Add platform specific locations
Platform.CURRENT_PLATFORM.getAdditionalLibraryPaths().forEach(p -> set.add(p));
// Add Java library path locations
String libpath = System.getProperty("java.library.path");
String libpathSep = System.getProperty("path.separator");
StringTokenizer nizer = new StringTokenizer(libpath, libpathSep);
while (nizer.hasMoreTokens()) {
String path = nizer.nextToken();
set.add(path);
}
return set;
}
/**
* Resets the library search path to match the system search paths.
*/
public static void reset() {
pathList.clear();
loadJavaLibraryPath();
private static synchronized Set<String> loadFromSavedState() {
Project project = AppInfo.getActiveProject();
if (project != null) {
SaveState saveState = project.getSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY);
if (saveState != null) {
String[] paths = saveState.getStrings(LIBRARY_SEARCH_PATH_STATE_NAME, null);
if (paths != null) {
return new LinkedHashSet<String>(Arrays.asList(paths));
}
}
}
return null;
}
/**
* Clears all paths.
*/
public static void clear() {
pathList.clear();
private static synchronized void saveState() {
Project project = AppInfo.getActiveProject();
if (project != null) {
SaveState saveState = project.getSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY);
if (saveState == null) {
saveState = new SaveState();
project.setSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY, saveState);
}
saveState.putStrings(LIBRARY_SEARCH_PATH_STATE_NAME, pathSet.toArray(new String[0]));
}
}
}

View File

@ -29,8 +29,7 @@ import org.apache.commons.lang3.ObjectUtils;
import ghidra.app.util.Option;
import ghidra.app.util.OptionUtils;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.LibrarySearchPathManager;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.*;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.*;
import ghidra.program.model.address.Address;
@ -57,11 +56,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
public static final String LINK_SEARCH_FOLDER_OPTION_NAME = "Project Library Search Folder";
static final String LINK_SEARCH_FOLDER_OPTION_DEFAULT = "";
public static final String LOCAL_LIBRARY_OPTION_NAME = "Load Local Libraries From Disk";
static final boolean LOCAL_LIBRARY_OPTION_DEFAULT = false;
public static final String LOAD_LIBRARY_OPTION_NAME = "Load Libraries From Disk";
static final boolean LOAD_LIBRARY_OPTION_DEFAULT = false;
public static final String SYSTEM_LIBRARY_OPTION_NAME = "Load System Libraries From Disk";
static final boolean SYSTEM_LIBRARY_OPTION_DEFAULT = false;
public static final String LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME = "Library Search Paths";
public static final String DEPTH_OPTION_NAME = "Recursive Library Load Depth";
static final int DEPTH_OPTION_DEFAULT = 1;
@ -143,8 +141,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (loadedPrograms.isEmpty()) {
return;
}
if (isLinkExistingLibraries(options) || isLoadLocalLibraries(options) ||
isLoadSystemLibraries(options)) {
if (isLinkExistingLibraries(options) || isLoadLibraries(options)) {
String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
List<DomainFolder> searchFolders = new ArrayList<>();
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
@ -177,18 +174,19 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
list.add(new Option(LINK_EXISTING_OPTION_NAME, LINK_EXISTING_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-linkExistingProjectLibraries"));
list.add(new Option(LINK_SEARCH_FOLDER_OPTION_NAME, LINK_SEARCH_FOLDER_OPTION_DEFAULT,
String.class, Loader.COMMAND_LINE_ARG_PREFIX + "-projectLibrarySearchFolder"));
list.add(new Option(LOCAL_LIBRARY_OPTION_NAME, LOCAL_LIBRARY_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-loadLocalLibraries"));
list.add(new Option(SYSTEM_LIBRARY_OPTION_NAME, SYSTEM_LIBRARY_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-loadSystemLibraries"));
list.add(new DomainFolderOption(LINK_SEARCH_FOLDER_OPTION_NAME,
Loader.COMMAND_LINE_ARG_PREFIX + "-projectLibrarySearchFolder"));
list.add(new Option(LOAD_LIBRARY_OPTION_NAME, LOAD_LIBRARY_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-loadLibraries"));
list.add(new LibrarySearchPathDummyOption(LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME));
list.add(new Option(DEPTH_OPTION_NAME, DEPTH_OPTION_DEFAULT, Integer.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-libraryLoadDepth"));
list.add(new Option(LIBRARY_DEST_FOLDER_OPTION_NAME, LIBRARY_DEST_FOLDER_OPTION_DEFAULT,
String.class, Loader.COMMAND_LINE_ARG_PREFIX + "-libraryDestinationFolder"));
list.add(new DomainFolderOption(LIBRARY_DEST_FOLDER_OPTION_NAME,
Loader.COMMAND_LINE_ARG_PREFIX + "-libraryDestinationFolder"));
return list;
}
@ -199,8 +197,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
for (Option option : options) {
String name = option.getName();
if (name.equals(LINK_EXISTING_OPTION_NAME) ||
name.equals(LOCAL_LIBRARY_OPTION_NAME) ||
name.equals(SYSTEM_LIBRARY_OPTION_NAME)) {
name.equals(LOAD_LIBRARY_OPTION_NAME)) {
if (!Boolean.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
@ -271,27 +268,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
/**
* Checks to see if local libraries should be loaded. Local libraries are libraries that live
* in the same directory as the imported program.
* Checks to see if libraries from disk should be loaded
*
* @param options a {@link List} of {@link Option}s
* @return True if local libraries should be loaded; otherwise, false
* @return True if libraries from disk should be loaded; otherwise, false
*/
protected boolean isLoadLocalLibraries(List<Option> options) {
return OptionUtils.getOption(LOCAL_LIBRARY_OPTION_NAME, options,
LOCAL_LIBRARY_OPTION_DEFAULT);
}
/**
* Checks to see if system libraries should be loaded. System libraries are libraries that live
* in the directories specified in the GUI path list.
*
* @param options a {@link List} of {@link Option}s
* @return True if system libraries should be loaded; otherwise, false
*/
protected boolean isLoadSystemLibraries(List<Option> options) {
return OptionUtils.getOption(SYSTEM_LIBRARY_OPTION_NAME, options,
SYSTEM_LIBRARY_OPTION_DEFAULT);
protected boolean isLoadLibraries(List<Option> options) {
return OptionUtils.getOption(LOAD_LIBRARY_OPTION_NAME, options,
LOAD_LIBRARY_OPTION_DEFAULT);
}
/**
@ -348,7 +332,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (project == null || libraryDestinationFolderPath == null) {
return null;
}
if (!isLoadLocalLibraries(options) && !isLoadSystemLibraries(options)) {
if (!isLoadLibraries(options)) {
return null;
}
return project.getProjectData().getFolder(libraryDestinationFolderPath);
@ -472,14 +456,11 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
Set<String> processed = new TreeSet<>(getLibraryNameComparator());
Queue<UnprocessedLibrary> unprocessed =
createUnprocessedQueue(libraryNameList, getLibraryLoadDepth(options));
boolean loadLocalLibraries = isLoadLocalLibraries(options);
boolean loadSystemLibraries = isLoadSystemLibraries(options);
boolean loadLibraries = isLoadLibraries(options);
List<FileSystemSearchPath> customSearchPaths =
getCustomLibrarySearchPaths(provider, options, log, monitor);
List<FileSystemSearchPath> localSearchPaths =
getLocalLibrarySearchPaths(provider, options, log, monitor);
List<FileSystemSearchPath> systemSearchPaths =
getSystemLibrarySearchPaths(options, log, monitor);
List<FileSystemSearchPath> searchPaths =
getLibrarySearchPaths(provider, options, log, monitor);
DomainFolder linkSearchFolder = getLinkSearchFolder(project, projectFolderPath, options);
String libraryDestFolderPath =
getLibraryDestinationFolderPath(project, projectFolderPath, options);
@ -507,9 +488,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
log.appendMsg("Found %s in %s...".formatted(libraryName, linkSearchFolder));
log.appendMsg("------------------------------------------------\n");
}
else if (!customSearchPaths.isEmpty() || !localSearchPaths.isEmpty() ||
!systemSearchPaths.isEmpty()) {
// Note that it is possible to have local (or system) search paths with those
else if (!customSearchPaths.isEmpty() || !searchPaths.isEmpty()) {
// Note that it is possible to have search paths with those
// options turned off (if shouldSearchAllPaths() is overridden to return true).
// In this case, we still want to process those libraries, but we
// do not want to save them, so they can be released.
@ -528,34 +508,15 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
loadedPrograms.add(loadedLibrary);
}
}
if (!loaded && !localSearchPaths.isEmpty()) {
log.appendMsg("Searching %d local path%s for library %s...".formatted(
localSearchPaths.size(), localSearchPaths.size() > 1 ? "s" : "",
libraryName));
if (!loaded && !searchPaths.isEmpty()) {
log.appendMsg("Searching %d path%s for library %s...".formatted(
searchPaths.size(), searchPaths.size() > 1 ? "s" : "", libraryName));
Loaded<Program> loadedLibrary = loadLibraryFromSearchPaths(libraryName,
provider, localSearchPaths, libraryDestFolderPath, unprocessed, depth,
provider, searchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor);
if (loadedLibrary != null) {
found = true;
if (loadLocalLibraries) {
loaded = true;
loadedPrograms.add(loadedLibrary);
}
else {
loadedLibrary.release(consumer);
}
}
}
if (!loaded && !systemSearchPaths.isEmpty()) {
log.appendMsg("Searching %d system path%s for library %s...".formatted(
systemSearchPaths.size(), systemSearchPaths.size() > 1 ? "s" : "",
libraryName));
Loaded<Program> loadedLibrary = loadLibraryFromSearchPaths(libraryName,
provider, systemSearchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor);
if (loadedLibrary != null) {
found = true;
if (loadSystemLibraries) {
if (loadLibraries) {
loaded = true;
loadedPrograms.add(loadedLibrary);
}
@ -586,7 +547,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (!success) {
release(loadedPrograms, consumer);
}
Stream.of(customSearchPaths, localSearchPaths, systemSearchPaths)
Stream.of(customSearchPaths, searchPaths)
.flatMap(Collection::stream)
.forEach(fsSearchPath -> {
if (!fsSearchPath.fsRef().isClosed()) {
@ -1068,52 +1029,20 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
/**
* Gets a {@link List} of priority-ordered local {@link FileSystemSearchPath}s used to search
* for libraries
* Gets a {@link List} of priority-ordered {@link FileSystemSearchPath}s used to search for
* libraries
*
* @param provider The {@link ByteProvider} of the program being loaded
* @param options The options
* @param log The log
* @param monitor A cancelable task monitor
* @return A {@link List} of priority-ordered local {@link FileSystemSearchPath}s used to
* search for libraries
* @return A {@link List} of priority-ordered {@link FileSystemSearchPath}s used to search for
* libraries
* @throws CancelledException if the user cancelled the load
*/
private List<FileSystemSearchPath> getLocalLibrarySearchPaths(ByteProvider provider,
private List<FileSystemSearchPath> getLibrarySearchPaths(ByteProvider provider,
List<Option> options, MessageLog log, TaskMonitor monitor) throws CancelledException {
if (!isLoadLocalLibraries(options) && !shouldSearchAllPaths(options)) {
return List.of();
}
List<FileSystemSearchPath> result = new ArrayList<>();
FileSystemService fsService = FileSystemService.getInstance();
FSRL providerFsrl = provider.getFSRL();
if (providerFsrl != null) {
try (RefdFile fileRef = fsService.getRefdFile(providerFsrl, monitor)) {
GFile parentFile = fileRef.file.getParentFile();
File f = new File(parentFile.getPath()); // File API will sanitize Windows-style paths
result.add(new FileSystemSearchPath(fileRef.fsRef.dup(), f.toPath()));
}
catch (IOException e) {
log.appendException(e);
}
}
return result;
}
/**
* Gets a {@link List} of priority-ordered system {@link FileSystemSearchPath}s used to search
* for libraries
*
* @param options The options
* @param log The log
* @param monitor A cancelable task monitor
* @return A {@link List} of priority-ordered system {@link FileSystemSearchPath}s used to
* search for libraries
* @throws CancelledException if the user cancelled the load
*/
private List<FileSystemSearchPath> getSystemLibrarySearchPaths(List<Option> options,
MessageLog log, TaskMonitor monitor) throws CancelledException {
if (!isLoadSystemLibraries(options) && !shouldSearchAllPaths(options)) {
if (!isLoadLibraries(options) && !shouldSearchAllPaths(options)) {
return List.of();
}
@ -1121,7 +1050,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
List<FileSystemSearchPath> result = new ArrayList<>();
boolean success = false;
try {
for (FSRL fsrl : LibrarySearchPathManager.getLibraryFsrlList(log, monitor)) {
for (FSRL fsrl : LibrarySearchPathManager.getLibraryFsrlList(provider, log, monitor)) {
if (fsService.isLocal(fsrl)) {
try {
FileSystemRef fileRef =

View File

@ -44,7 +44,7 @@ public class LibraryPathsDialog extends AbstractPathsDialog {
protected PathnameTablePanel newPathnameTablePanel() {
// disable edits, add to top, ordered
PathnameTablePanel panel =
new PathnameTablePanel(loadPaths(), this::reset, false, true, true);
new PathnameTablePanel(loadPaths(), this::reset, false, true, true, true);
panel.setFileChooserProperties("Select Directory or Filesystem",
"LibrarySearchDirectory", GhidraFileChooserMode.FILES_AND_DIRECTORIES, false, null);
return panel;

View File

@ -34,7 +34,6 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.FileImporterService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.LibrarySearchPathManager;
import ghidra.app.util.opinion.LoaderMap;
import ghidra.app.util.opinion.LoaderService;
import ghidra.formats.gfilesystem.FSRL;
@ -44,7 +43,6 @@ import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.main.*;
import ghidra.framework.main.datatree.*;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@ -119,23 +117,6 @@ public class ImporterPlugin extends Plugin
setupBatchImportAction();
}
@Override
public void readConfigState(SaveState saveState) {
super.readConfigState(saveState);
String[] paths = saveState.getStrings("library search paths", null);
if (paths != null) {
LibrarySearchPathManager.setLibraryPaths(paths);
}
}
@Override
public void writeConfigState(SaveState saveState) {
super.writeConfigState(saveState);
String[] paths = LibrarySearchPathManager.getLibraryPaths();
saveState.putStrings("library search paths", paths);
}
@Override
protected void dispose() {
super.dispose();

View File

@ -97,12 +97,7 @@ public class DyldCacheExtractLoader extends MachoLoader {
}
@Override
protected boolean isLoadLocalLibraries(List<Option> options) {
return false;
}
@Override
protected boolean isLoadSystemLibraries(List<Option> options) {
protected boolean isLoadLibraries(List<Option> options) {
return false;
}

View File

@ -97,12 +97,7 @@ public class MachoFileSetExtractLoader extends MachoLoader {
}
@Override
protected boolean isLoadLocalLibraries(List<Option> options) {
return false;
}
@Override
protected boolean isLoadSystemLibraries(List<Option> options) {
protected boolean isLoadLibraries(List<Option> options) {
return false;
}

View File

@ -57,6 +57,7 @@ public class PathnameTablePanel extends JPanel {
private GhidraFileFilter filter;
private boolean addToTop;
private boolean ordered;
private boolean supportsDotPath;
private Callback resetCallback;
@ -95,12 +96,15 @@ public class PathnameTablePanel extends JPanel {
* false.
* @param ordered true if the order of entries matters. If so, up and down buttons are provided
* so the user may arrange the entries. If not, entries are sorted alphabetically.
* @param supportsDotPath true if the add button should support adding the "." path. If so,
* the user will be prompted to choose from a file browser, or adding ".".
*/
public PathnameTablePanel(String[] paths, Callback resetCallback, boolean enableEdits,
boolean addToTop, boolean ordered) {
boolean addToTop, boolean ordered, boolean supportsDotPath) {
super(new BorderLayout(5, 5));
this.addToTop = addToTop;
this.ordered = ordered;
this.supportsDotPath = supportsDotPath;
this.resetCallback = resetCallback;
tableModel = new PathnameTableModel(paths, enableEdits);
create();
@ -316,6 +320,17 @@ public class PathnameTablePanel extends JPanel {
private void add() {
if (supportsDotPath && !Arrays.stream(getPaths()).anyMatch(p -> p.equals("."))) {
int selection =
OptionDialog.showOptionNoCancelDialog(this, "Add Path", "Choose how to add a path:",
"File Chooser", "Program's Import Location", OptionDialog.QUESTION_MESSAGE);
if (selection == OptionDialog.OPTION_TWO) {
tableModel.addPaths(new String[] { "." }, addToTop, !ordered);
return;
}
}
GhidraFileChooser fileChooser = new GhidraFileChooser(this);
fileChooser.setMultiSelectionEnabled(allowMultiFileSelection);
fileChooser.setFileSelectionMode(fileChooserMode);
@ -363,7 +378,7 @@ public class PathnameTablePanel extends JPanel {
String confirmation = """
<html><body width="200px">
Are you sure you would like to reset the paths to the default list?
This will remove all paths manually added.
This will remove all paths manually added and cannot be later cancelled.
</html>""";
String header = "Reset Paths?";

View File

@ -130,6 +130,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
[<a href="#commit">-commit [&quot;&lt;comment&gt;&quot;]</a>]
[<a href="#okToDelete">-okToDelete</a>]
[<a href="#max-cpu">-max-cpu &lt;max cpu cores to use&gt;</a>]
[<a href="#librarySearchPaths">-librarySearchPaths &lt;path1&gt;[;&lt;path2&gt;...]</a>]
[<a href="#loader">-loader &lt;desired loader name&gt;</a>]
</PRE>
@ -583,6 +584,14 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
</LI>
<br><br>
<LI>
<a name="librarySearchPaths"><typewriter>-librarySearchPaths &lt;path1&gt;[;&lt;path2&gt;...]</typewriter></a><br>
Specifies an ordered list of library search paths to use during import instead of the default.
Search paths may be either full system paths or "FSRLs".
</LI>
<br><br>
<LI>
<a name="loader"><typewriter>-loader &lt;desired loader name&gt;</typewriter></a><br>
@ -606,8 +615,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
<LI><typewriter>-loader-anchorLabels &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-linkExistingProjectLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-projectLibrarySearchFolder &lt;project path&gt;</typewriter></LI>
<LI><typewriter>-loader-loadLocalLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-loadSystemLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-loadLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-libraryLoadDepth &lt;depth&gt;</typewriter></LI>
<LI><typewriter>-loader-libraryDestinationFolder &lt;project path&gt;</typewriter></LI>
<LI><typewriter>-loader-applyRelocations &lt;true|false&gt;</typewriter></LI>
@ -623,8 +631,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
<LI><typewriter>-loader-anchorLabels &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-linkExistingProjectLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-projectLibrarySearchFolder &lt;project path&gt;</typewriter></LI>
<LI><typewriter>-loader-loadLocalLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-loadSystemLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-loadLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-libraryLoadDepth &lt;depth&gt;</typewriter></LI>
<LI><typewriter>-loader-libraryDestinationFolder &lt;project path&gt;</typewriter></LI>
<LI><typewriter>-loader-ordinalLookup &lt;true|false&gt;</typewriter></LI>
@ -637,8 +644,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
<LI><typewriter>-loader-anchorLabels &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-linkExistingProjectLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-projectLibrarySearchFolder &lt;project path&gt;</typewriter></LI>
<LI><typewriter>-loader-loadLocalLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-loadSystemLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-loadLibraries &lt;true|false&gt;</typewriter></LI>
<LI><typewriter>-loader-libraryLoadDepth &lt;depth&gt;</typewriter></LI>
<LI><typewriter>-loader-libraryDestinationFolder &lt;project path&gt;</typewriter></LI>
</UL>

View File

@ -59,7 +59,7 @@ public class PathnameTablePanelTest extends AbstractDockingTest {
public void setUp() throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// enable edits, add to bottom, ordered
panel = new PathnameTablePanel(tablePaths, () -> reset(), true, false, true);
panel = new PathnameTablePanel(tablePaths, () -> reset(), true, false, true, false);
table = panel.getTable();
frame = new JFrame("Test");
frame.getContentPane().add(panel);

View File

@ -35,7 +35,7 @@
[-propertiesPath &quot;&lt;path1&gt;[;&lt;path2&gt;...]&quot;]
[-log &lt;path to log file&gt;] [-scriptlog &lt;path to script log file&gt;]
[-overwrite] [-recursive [&lt;depth&gt;]] [-readOnly] [-deleteProject]
[-noanalysis]
[-noanalysis] [-librarySearchPaths &lt;path1&gt;[;&lt;path2&gt;...]]
[-processor &lt;languageID&gt;] [-cspec &lt;compilerSpecID&gt;]
[-analysisTimeoutPerFile &lt;timeout in seconds&gt;]
[-keystore &lt;KeystorePath&gt;] [-connect [&lt;userID&gt;]]