package aQute.jpm.main;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.nio.charset.*;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.jar.*;
import java.util.regex.*;
import aQute.bnd.osgi.*;
import aQute.jpm.lib.*;
import aQute.jpm.lib.JustAnotherPackageManager.UpdateMemo;
import aQute.jpm.lib.Service;
import aQute.jpm.platform.*;
import aQute.lib.base64.*;
import aQute.lib.collections.*;
import aQute.lib.data.*;
import aQute.lib.getopt.*;
import aQute.lib.hex.*;
import aQute.lib.io.*;
import aQute.lib.justif.*;
import aQute.lib.settings.*;
import aQute.lib.strings.*;
import aQute.libg.command.*;
import aQute.libg.glob.*;
import aQute.libg.reporter.*;
import aQute.service.library.*;
import aQute.service.library.Library.Program;
import aQute.service.library.Library.Revision;
import aQute.struct.struct.Error;
/**
* The command line interface to JPM
*/
@Description("Just Another Package Manager (for Java)\nMaintains a local repository of Java jars (apps or libs). Can automatically link these jars to an OS command or OS service. For more information see https://www.jpm4j.org/#!/md/jpm")
public class Main extends ReporterAdapter {
private static final String JPM_CONFIG_BIN = "jpm.config.bin";
private static final String JPM_CONFIG_HOME = "jpm.config.home";
static Pattern ASSIGNMENT = Pattern.compile("\\s*([-\\w\\d_.]+)\\s*(?:=\\s*([^\\s]+)\\s*)?");
public final static Pattern URL_PATTERN = Pattern.compile("[a-zA-Z][0-9A-Za-z]{1,8}:.+");
public final static Pattern BSNID_PATTERN = Pattern.compile("([-A-Z0-9_.]+?)(-\\d+\\.\\d+.\\d+)?",
Pattern.CASE_INSENSITIVE);
File base = new File(System.getProperty("user.dir"));
Settings settings;
boolean userMode = false;
JustAnotherPackageManager jpm;
final PrintStream err;
final PrintStream out;
File sm;
private String url;
private JpmOptions options;
static String encoding = System.getProperty("file.encoding");
int width = 120; // characters
int tabs[] = {
40, 48, 56, 64, 72, 80, 88, 96, 104, 112
};
static {
if (encoding == null)
encoding = Charset.defaultCharset().name();
}
/**
* Default constructor
*
* @throws UnsupportedEncodingException
*/
public Main() throws UnsupportedEncodingException {
super(new PrintStream(System.err, true, encoding));
err = new PrintStream(System.err, true, encoding);
out = new PrintStream(System.out, true, encoding);
}
/**
* Main entry
*
* @throws Exception
*/
public static void main(String args[]) throws Exception {
Main jpm = new Main();
try {
jpm.run(args);
}
finally {
jpm.err.flush();
jpm.out.flush();
}
}
/**
* Show installed binaries
*/
public interface ModifyService extends ModifyCommand {
@Description("Provide arguments to the service when started")
String args();
@Description("Set the log path. Log output will go to this file, otherwise in a reserved directory")
String log();
@Description("Set the log path")
String work();
@Description("Set the user id for the service when running")
String user();
@Description("If set, will be started at boot time after the given services have been started. Specify boot if there are no other dependencies.")
List<String> after();
@Description("Commands executed after the service exits, will run under root.")
String epilog();
@Description("Commands executed just before the service starts while still root.")
String prolog();
}
public interface ModifyCommand {
@Description("Provide or override the JVM arguments")
String jvmargs();
@Description("Provide the name of the main class used to launch this command or service in fully qualified form, e.g. aQute.main.Main")
String main();
@Description("Provide the name of the command or service")
String name();
@Description("Provide the title of the command or service")
String title();
@Description("Collect permission requests and print them at the end of a run. This can provide detailed information about what resources the command is using.")
boolean trace();
@Description("Java is default started in console mode, you can specify to start it in windows mode (or javaw)")
boolean windows();
}
/**
* Services
*/
@Arguments(arg = {
"[name]"
})
@Description("Manage the JPM services. Without arguments and options, this will show all the current services. Careful, if --remove is used all services are removed without any parameters.")
public interface ServiceOptions extends Options, ModifyService {
@Description("Create a new service on an existing artifact")
String create();
@Description("Remove the given service")
boolean remove();
@Description("Consider staged versions. Normally only masters are considered.")
boolean staged();
@Description("Update the service with the latest master/staged version (see --staged)")
boolean update();
@Description("Specify the coordinate of the service, identifies the main binary")
String coordinates();
}
/**
* Commands
*/
@Arguments(arg = "[command]")
@Description("Manage the commands that have been installed so far")
public interface CommandOptions extends Options, ModifyCommand {
String create();
@Description("Remove the given service")
boolean remove();
}
@Description("Remove jpm and all created data from the system (including commands and services). "
+ "Without the --force flag only list the elements that would be deleted.")
public interface deinitOptions extends Options {
@Description("Actually remove jpm from the system")
boolean force();
}
/**
* garbage collect commands and service
*/
@Arguments(arg = {})
@Description("Garbage collect the cache (remove useless dependencies)")
public interface GCOptions extends Options {}
/**
* Main options
*/
@Arguments(arg = "cmd ...")
@Description("Options valid for all commands. Must be given before sub command")
interface JpmOptions extends Options {
@Description("Print exception stack traces when they occur.")
boolean exceptions();
@Description("Trace on.")
boolean trace();
@Description("Be pedantic about all details.")
boolean pedantic();
@Description("Specify a new base directory (default working directory).")
String base();
@Description("Do not return error status for error that match this given regular expression.")
String[] failok();
@Description("Remote library url (can also be permanently set with 'jpm settings library.url=...'")
String library();
@Description("Specify the home directory of jpm. (can also be permanently set with 'jpm settings jpm.home=...'")
String home();
@Description("Wait for a key press, might be useful when you want to see the result before it is overwritten by a next command")
boolean key();
@Description("Show the release notes")
boolean release();
@Description("Run jpm with local configurations (repository -> cache.local setting or platform default, binaries -> bin.local or platform default)")
boolean user();
@Description("Run jpm with global configurations (repository -> cache.global setting or platform default, binaries -> bin.global or platform default)")
boolean global();
@Description("Change settings file (one-shot)")
String settings();
@Description("Specify executables directory (one-shot)")
String bindir();
@Description("Specify the platform (this is mainly for testing purposes). Is either WINDOWS, MACOS, or LINUX")
Platform.Type os();
boolean xtesting();
int width();
}
/**
* Initialize the repository and other global vars.
*
* @param opts
* the options
* @throws InterruptedException
* @throws IOException
*/
@Description("Just Another Package Manager for Java (\"jpm help jpm\" to see a list of global options)")
public void _jpm(JpmOptions opts) throws IOException {
try {
setExceptions(opts.exceptions());
setTrace(opts.trace());
setPedantic(opts.pedantic());
Platform platform = Platform.getPlatform(this, opts.os());
if (opts.base() != null)
base = IO.getFile(base, opts.base());
if (opts.settings() != null) {
settings = new Settings(opts.settings());
trace("Using settings file: %s", opts.settings());
} else {
settings = new Settings(platform.getConfigFile());
trace("Using settings file: %s", platform.getConfigFile());
}
File homeDir;
File binDir;
String home = settings.get(JPM_CONFIG_HOME);
String bin = settings.get(JPM_CONFIG_BIN);
if (opts.home() != null) {
trace("home set");
homeDir = IO.getFile(base, opts.home());
binDir = new File(homeDir, "bin");
} else if (opts.user()) {
trace("user set");
homeDir = platform.getLocal();
binDir = new File(homeDir, "bin");
} else if (!opts.global() && home != null) {
trace("global or in settings");
homeDir = new File(home);
binDir = new File(bin);
} else {
trace("default");
homeDir = platform.getGlobal();
binDir = platform.getGlobalBinDir();
}
trace("home=%s, bin=%s", homeDir, binDir);
if (opts.bindir() != null) {
trace("bindir set");
binDir = new File(opts.bindir());
if (!binDir.isAbsolute())
binDir = new File(base, opts.bindir());
binDir = binDir.getAbsoluteFile();
} else if (bin != null && !opts.user() && !opts.global()) {
binDir = new File(bin);
}
trace("home=%s, bin=%s", homeDir, binDir);
url = opts.library();
if (url == null)
url = settings.get("library.url");
jpm = new JustAnotherPackageManager(this, platform, homeDir, binDir);
platform.setJpm(jpm);
jpm.setLibrary(url == null ? null : new URI(url));
try {
this.options = opts;
if (opts.xtesting())
jpm.setUnderTest();
CommandLine handler = opts._command();
List<String> arguments = opts._();
if (arguments.isEmpty()) {
Justif j = new Justif();
Formatter f = j.formatter();
handler.help(f, this);
err.println(j.wrap());
} else {
String cmd = arguments.remove(0);
String help = handler.execute(this, cmd, arguments);
if (help != null) {
err.println(help);
}
}
if (options.width() > 0)
this.width = options.width();
}
finally {
jpm.close();
}
}
catch (InvocationTargetException t) {
Throwable tt = t;
while (tt instanceof InvocationTargetException)
tt = ((InvocationTargetException) tt).getTargetException();
exception(tt, "%s", tt.getMessage());
}
catch (Throwable t) {
exception(t, "Failed %s", t);
}
finally {
// Check if we need to wait for it to finish
if (opts.key()) {
System.out.println("Hit a key to continue ...");
System.in.read();
}
}
if (!check(opts.failok())) {
System.exit(getErrors().size());
}
}
/**
* Install a jar options
*/
@Arguments(arg = {
"command|service"
})
@Description("Install a jar into the repository. If the jar defines a number of headers it can also be installed as a command and/or a service. "
+ "If not, additional information such as the name of the command and/or the main class must be specified with the appropriate flags.")
public interface installOptions extends ModifyCommand, Options {
// @Description("Ignore command and service information")
// boolean ignore(); // pl: not used
@Description("Force overwrite of existing command")
boolean force();
// @Description("Require a master version even when version is specified")
// boolean master(); // pl: not used
// @Description("Ignore digest")
// boolean xdigest(); // pl: not used
// @Description("Run service (if present) under the given user name, default is the name of the service")
// String user(); // pl: not used
// /**
// * If specified, will install a revision with the given name and
// version
// * and then add any command/service to the system.
// */
// String bsn(); // pl: not used
// /**
// * Specify a version range for the artifact.
// *
// * @return
// */
// Version version(); // pl: not used
/**
* Install a file and extra commands
*/
@Description("Install jar without resolving dependencies with http://www.jpm4j.org")
boolean local();
/**
* Do not look for command
*/
@Description("Install jar but do not look for a command in the jar")
boolean ignore();
}
/**
* A better way to install
*/
@Description("Install an artifact from a url, file, or http://www.jpm4j.org")
public void _install(installOptions opts) throws Exception {
if (!this.options.user() && !jpm.hasAccess()) {
error("No write acces, might require administrator or root privileges (sudo in *nix)");
return;
}
for (String coordinate : opts._()) {
trace("install %s", coordinate);
File file = IO.getFile(base, coordinate);
if (file.isFile()) {
coordinate = file.toURI().toString();
trace("is existing file: %s", coordinate);
}
ArtifactData artifact = jpm.getCandidate(coordinate);
trace("candidate %s", artifact);
if (artifact == null) {
if (jpm.isWildcard(coordinate))
error("no candidate found for %s", coordinate);
else
error("no candidate found for %s, you could try %s@* to also see staged and withdrawn revisions",
coordinate, coordinate);
} else {
if (!opts.ignore()) {
CommandData cmd = jpm.parseCommandData(artifact);
updateCommandData(cmd, opts);
trace("main=%s, name=%s", cmd.main, cmd.name);
if (cmd.main != null) {
if (cmd.name == null && !artifact.local) {
cmd.name = artifact.coordinate.getArtifactId();
}
List<Error> errors = cmd.validate();
if (!errors.isEmpty()) {
error("Command not valid");
for (Error error : errors) {
error("[%s] %s %s %s %s", error.code, error.description, error.path, error.failure,
error.value);
}
} else {
String result = jpm.createCommand(cmd, opts.force());
if (result != null) {
error("[%s] %s", coordinate, result);
}
}
} else
error("No main class found. Please specify");
}
}
}
}
@Description("Manage the jpm4j services")
public void _service(ServiceOptions opts) throws Exception {
if (opts._().isEmpty()) {
for (ServiceData sd : jpm.getServices())
print(sd);
return;
}
List<String> cmdline = opts._();
String name = cmdline.remove(0);
Service s = jpm.getService(name);
if (opts.remove()) {
if (!jpm.hasAccess()) {
error("No write access to remove service %s", name);
return;
}
if (s == null) {
error("No such service %s to remove", name);
return;
}
s.stop();
s.remove();
return;
}
if (opts.create() != null) {
if (s != null) {
error("Service already exists, cannot be created: %s. Update or remove it first", name);
return;
}
ArtifactData target = jpm.getCandidate(opts.create());
if (target == null)
return;
ServiceData data = null; // TODO target.service;
updateServiceData(data, opts);
String result = jpm.createService(data);
if (result != null)
error("Create service failed: %s", result);
return;
}
if (s == null) {
error("No such service: %s", name);
return;
}
ServiceData data = s.getServiceData();
if (updateServiceData(data, opts) || opts.coordinates() != null || opts.update()) {
if (!jpm.hasAccess()) {
error("No write access to update service %s", name);
return;
}
//
// Check if we have to update the underlying artifact
// This is triggered by --coordinate, which provides
// the new coordinate or just --update which reuses the
// old coordinate without version
//
if (opts.coordinates() != null || opts.update()) {
String coordinates = opts.coordinates();
if (coordinates == null) {
error("No coordinate found in old service record");
return;
}
int n = coordinates.indexOf('@');
if (n > 0)
coordinates = coordinates.substring(0, n);
trace("Updating from coordinate: %s", coordinates);
ArtifactData target = jpm.getCandidate(coordinates);
if (target == null) {
error("No candidates found for %s (%s)", coordinates, opts.staged() ? "staged" : "only masters");
return;
}
// data.dependencies.clear();
// data.dependencies.add(target.file);
// data.dependencies.addAll(target.dependencies);
// data.coordinates = coordinates;
}
String result = jpm.createService(data);
if (result != null)
error("Update service failed: %s", result);
else if (s.isRunning())
warning("Changes will not affect the currently running process");
}
Data.details(data, out);
}
private boolean updateServiceData(ServiceData data, ModifyService opts) {
boolean update = false;
if (opts.args() != null) {
data.args = opts.args();
update = true;
}
if (opts.prolog() != null) {
data.prolog = opts.prolog();
update = true;
}
if (opts.epilog() != null) {
data.epilog = opts.epilog();
update = true;
}
if (opts.log() != null) {
data.log = IO.getFile(base, opts.log()).getAbsolutePath();
update = true;
}
if (opts.work() != null) {
data.work = IO.getFile(base, opts.work()).getAbsolutePath();
update = true;
}
if (opts.after() != null) {
data.after = opts.after();
update = true;
}
if (opts.user() != null) {
data.user = opts.user();
update = true;
}
return updateCommandData((CommandData) data, opts) || update;
}
private boolean updateCommandData(CommandData data, ModifyCommand opts) {
boolean update = false;
if (opts.main() != null) {
data.main = opts.main();
update = true;
}
if (opts.jvmargs() != null) {
data.jvmArgs = opts.jvmargs();
update = true;
}
if (opts.name() != null) {
data.name = opts.name();
update = true;
}
if (opts.title() != null) {
data.title = opts.title();
update = true;
}
if (opts.trace() != data.trace) {
data.trace = opts.trace();
update = true;
}
if (opts.windows() != data.windows) {
data.windows = opts.windows();
update = true;
}
return update;
}
private void print(ServiceData sd) throws Exception {
Service s = jpm.getService(sd.name);
out.printf("%-40s (%s) %s%n", sd.name, s.isRunning() ? "runs " : "stopped", sd.args);
}
@Description("Manage the jpm4j commands")
public void _command(CommandOptions opts) throws Exception {
if (opts.remove()) {
Instructions instrs = new Instructions(opts._());
for (CommandData cmd : jpm.getCommands()) {
if (instrs.matches(cmd.name)) {
jpm.deleteCommand(cmd.name);
}
}
return;
}
if (opts._().isEmpty()) {
print(jpm.getCommands());
return;
}
String cmd = opts._().get(0);
CommandData data = jpm.getCommand(cmd);
if (data == null) {
error("Not found: %s", cmd);
} else {
CommandData newer = new CommandData();
JustAnotherPackageManager.xcopy(data, newer);
if (updateCommandData(newer, opts)) {
jpm.deleteCommand(data.name);
String result = jpm.createCommand(newer, true);
if (result != null)
error("Failed to update command %s: %s", cmd, result);
}
print(newer);
}
}
private void print(CommandData command) throws Exception {
Justif j = new Justif(width, tabs);
Formatter f = j.formatter();
f.format("%n[%s]%n", command.name);
f.format("%s\n\n", Strings.display(command.description, command.title));
f.format("SHA-1\t1%s%n", Hex.toHexString(command.sha));
f.format("Bundle Symbolic Name\t1%s%n", Strings.display(command.bsn, "<no bsn>"));
f.format("Version\t1%s%n", Strings.display(command.version, "<no version>"));
f.format("JVMArgs\t1%s%n", "JVM Args", command.jvmArgs);
f.format("Main class\t1%s%n", command.main);
f.format("Install time\t1%s%n", new Date(command.time));
f.format("Path\t1%s%n", command.bin);
f.format("Installed\t1%s%n", command.installed);
f.format("JRE\t1%s%n", Strings.display(command.java, "<default>"));
f.format("Trace\t1%s%n", command.trace ? "On" : "Off");
list(f, "Dependencies", jpm.toString(command.dependencies));
list(f, "Runbundles", jpm.toString(command.runbundles));
out.append(j.wrap());
}
private void list(Formatter f, String title, List< ? > elements) {
if (elements == null || elements.isEmpty())
return;
f.format("[%s]\t1", title);
String del = "";
for (Object element : elements) {
f.format("%s%s", del, element);
del = "\f";
}
f.format("%n");
}
private void print(List<CommandData> commands) {
Justif j = new Justif(width, tabs);
Formatter f = j.formatter();
for (CommandData command : commands) {
f.format("%s\t1%s%n", command.name, Strings.display(command.description, command.title));
}
out.append(j.wrap());
}
@Description("Clean up any stale data, including any downloaded files not used in any commands")
public void _gc(GCOptions opts) throws Exception {
if (!jpm.hasAccess()) {
error("No write acces, might require administrator or root privileges (sudo in *nix)");
return;
}
jpm.gc();
}
@Description("Remove jpm from the system by deleting all artifacts and metadata")
public void _deinit(deinitOptions opts) throws Exception {
jpm.deinit(out, opts.force());
}
/**
* Main entry for the command line
*
* @param args
* @throws Exception
*/
public void run(String[] args) throws Exception {
CommandLine cl = new CommandLine(this);
ExtList<String> list = new ExtList<String>(args);
String help = cl.execute(this, "jpm", list);
check();
if (help != null)
err.println(help);
}
/**
* Setup jpm to run on this system.
*
* @throws Exception
*/
@Description("Install jpm on the current system")
interface InitOptions extends Options {}
@Description("Install jpm on the current system")
public void _init(InitOptions opts) throws Exception {
if (!jpm.hasAccess()) {
error("No write acces, might require administrator or root privileges (sudo in *nix)");
return;
}
jpm.getPlatform().init();
try {
String s = System.getProperty("java.class.path");
if (s == null) {
error("Cannot initialize because not clear what the command jar is from java.class.path: %s", s);
return;
}
String parts[] = s.split(File.pathSeparator);
s = parts[0];
try {
File f = new File(s).getAbsoluteFile();
if (f.exists()) {
CommandLine cl = new CommandLine(this);
String help = cl.execute(this, "install", Arrays.asList("-fl", f.getAbsolutePath()));
if (help != null) {
error(help);
return;
}
String completionInstallResult = jpm.getPlatform().installCompletion(this);
if (completionInstallResult != null)
trace(completionInstallResult);
settings.put(JPM_CONFIG_BIN, jpm.getBinDir().getAbsolutePath());
settings.put(JPM_CONFIG_HOME, jpm.getHomeDir().getAbsolutePath());
settings.save();
if (jpm.getPlatform().hasPost()) {
Command cmd = new Command();
cmd.add(new File(jpm.getBinDir(), "jpm").getAbsolutePath());
cmd.add("_postinstall");
cmd.setTimeout(5, TimeUnit.SECONDS);
StringBuffer stdout = new StringBuffer();
StringBuffer stderr = new StringBuffer();
System.out.println("post : " + cmd);
int result = cmd.execute(stdout, stderr);
if (result != 0) {
Justif j = new Justif(80, 5, 10, 20);
if (stdout.length() != 0)
j.formatter().format("stdout: $-%n", stdout);
if (stderr.length() != 0)
j.formatter().format("stderr: $-%n", stderr);
error("Failed to run platform init, exit code %s.%n%s", result, j.wrap());
} else
out.append(stdout);
}
out.println("Home dir " + jpm.getHomeDir());
out.println("Bin dir " + jpm.getBinDir());
} else
error("Cannot find the jpm jar from %s", f);
}
catch (InvocationTargetException e) {
exception(e.getTargetException(), "Could not install jpm, %s", e.getTargetException().getMessage());
if (isExceptions())
e.printStackTrace();
}
}
catch (Throwable e) {
e.printStackTrace();
}
}
/**
* Show platform
*/
@Arguments(arg = {
"[cmd]", "..."
})
@Description("Show the name of the platform, or more specific information")
public interface PlatformOptions extends Options {
@Description("Show detailed information")
boolean verbose();
}
/**
* Show the platform info.
*
* @param opts
* @throws IOException
* @throws Exception
*/
@Description("Show platform information")
public void _platform(PlatformOptions opts) throws IOException, Exception {
CommandLine cli = opts._command();
List<String> cmds = opts._();
if (cmds.isEmpty()) {
if (opts.verbose()) {
Justif j = new Justif(80, 30, 40, 50, 60);
jpm.getPlatform().report(j.formatter());
out.append(j.wrap());
} else
out.println(jpm.getPlatform().getName());
} else {
String execute = cli.execute(jpm.getPlatform(), cmds.remove(0), cmds);
if (execute != null) {
out.append(execute);
}
}
}
/**
* Show all the installed VMs
*/
@Description("Manage installed VMs ")
interface VMOptions extends Options {
String add();
}
public void _vm(VMOptions opts) throws Exception {
if (opts.add() != null) {
File f = IO.getFile(base, opts.add()).getCanonicalFile();
if (!f.isDirectory()) {
error("No such directory %s to add a JVM", f);
} else {
jpm.addVm(f);
}
}
SortedSet<JVM> vms = jpm.getVMs();
for (JVM jvm : vms) {
out.printf("%-30s %-5s %-20s %-30s %s\n", jvm.name, jvm.platformVersion, jvm.version, jvm.vendor, jvm.path);
}
}
/**
* Start a service.
*
* @param options
* @throws Exception
*/
@Arguments(arg = {
"service"
})
@Description("Start a service")
interface startOptions extends Options {
boolean clean();
}
@Description("Start a service")
public void _start(startOptions options) throws Exception {
if (!jpm.hasAccess()) {
error("No write acces, might require administrator or root privileges (sudo in *nix)");
return;
}
for (String s : options._()) {
Service service = jpm.getService(s);
if (service == null)
error("Non existent service %s", s);
else {
if (!service.isRunning()) {
try {
ServiceData d = service.getServiceData();
trace("starting %s as user %s, lock=%s, log=%s", d.name, d.user, d.lock, d.log);
if (options.clean())
service.clear();
String result = service.start();
if (result != null)
error("Failed to start: %s", result);
}
catch (Exception e) {
exception(e, "Could not start service %s due to %s", s, e.getMessage());
}
} else
warning("Service %s already running", s);
}
}
}
/**
* Restart a service.
*
* @param options
* @throws Exception
*/
@Arguments(arg = "service")
@Description("Restart a service")
public interface RestartOptions extends Options {}
@Description("Restart a service")
public void _restart(RestartOptions options) throws Exception {
for (String s : options._()) {
Service service = jpm.getService(s);
if (service == null)
error("Non existent service %s", s);
else {
try {
if (service.isRunning()) {
service.stop();
}
String result = service.start();
if (result != null)
error("Failed to start: %s", result);
}
catch (Exception e) {
exception(e, "Could not start service %s due to %s", s, e.getMessage());
}
}
}
}
/**
* Trace a service.
*
* @param options
* @throws Exception
*/
@Arguments(arg = {
"service", "[on|off]"
})
public interface traceOptions extends Options {
boolean continuous();
}
@Description("Trace a service")
public void _trace(traceOptions options) throws Exception {
List<String> args = options._();
String s = args.remove(0);
boolean on = args.isEmpty() || !"off".equalsIgnoreCase(args.remove(0));
Service service = jpm.getService(s);
if (service == null)
error("Non existent service %s", s);
else {
try {
if (!service.isRunning())
error("First start the service to trace it");
else {
String result = service.trace(on);
if (result != null)
error("Failed to trace: %s", result);
}
}
catch (Exception e) {
exception(e, "Could not trace service %s due to %s", s, e.getMessage());
}
}
}
/**
* Stop a service.
*
* @param options
* @throws Exception
*/
@Description("Stop a service")
public interface StopOptions extends Options {}
@Description("Stop a service")
public void _stop(StopOptions options) throws Exception {
for (String s : options._()) {
Service service = jpm.getService(s);
if (service == null)
error("Non existent service %s", s);
else {
if (service.isRunning()) {
try {
String result = service.stop();
if (result != null)
error("Failed to stop: %s", result);
}
catch (Exception e) {
exception(e, "Could not stop service %s due to %s", s, e.getMessage());
}
} else
warning("Service %s not running", s);
}
}
}
/**
* Status a service.
*
* @param options
* @throws Exception
*/
@Description("Status of a service")
@Arguments(arg = {
"service", "[service]", "..."
})
interface statusOptions extends Options {
@Description("Prints status for the service(s) every second")
boolean continuous();
}
@Description("Status of a service/services")
public void _status(statusOptions options) throws InterruptedException {
while (true) {
for (String s : options._()) {
String runs = "false";
String status = "no service";
try {
Service service = jpm.getService(s);
if (service != null) {
runs = service.isRunning() + "";
status = service.status();
}
}
catch (Exception e) {
status = e.getMessage();
exception(e, "could not fetch status information from service %s, due to %s", s, e.getMessage());
}
out.printf("%-40s %8s %s\r", s, runs, status);
}
if (!options.continuous()) {
out.println();
return;
}
Thread.sleep(1000);
}
}
/**
* Show the current version
*
* @throws IOException
*/
@Arguments(arg = {})
@Description("Show the current version. The qualifier represents the build date.")
interface VersionOptions extends Options {
}
@Description("Show the current version of jpm")
public void _version(VersionOptions options) throws IOException {
Enumeration<URL> urls = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
trace("found manifest %s", url);
Manifest m = new Manifest(url.openStream());
String name = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
if (name != null && name.trim().equals("biz.aQute.jpm.run")) {
out.println(m.getMainAttributes().getValue(Constants.BUNDLE_VERSION));
return;
}
}
error("No version found in jar");
}
// /**
// * Manage the interface to the library
// *
// * @throws Exception
// */
//
// public void _lib(LibraryCommandOptions options) throws Exception {
// LibraryCommand library = new LibraryCommand(options, base, out,
// settings);
//
// CommandLine cline = options._command();
// List<String> _ = options._();
// if (_.isEmpty())
// cline.execute(library, "info", _);
// else
// cline.execute(library, _.remove(0), _);
//
// }
/**
* @throws Exception
*/
interface KeysOptions extends Options {
boolean secret();
boolean pem();
boolean hex();
boolean extended();
}
@Description("Show the jpm machine keys")
public void _keys(KeysOptions opts) throws Exception {
boolean any = opts.pem() || opts.extended() || opts.hex();
if (opts.extended()) {
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(settings.getPrivateKey());
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(settings.getPublicKey());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
privateKey.getAlgorithm();
if (opts.secret())
out.format("private %s", privateKey);
out.format("public %s", publicKey);
}
if (opts.hex()) {
if (opts.secret())
out.format("private %s", Hex.toHexString(settings.getPrivateKey()));
out.format("public %s", Hex.toHexString(settings.getPublicKey()));
}
if (opts.pem() || !any) {
formatKey(settings.getPublicKey(), "PUBLIC");
if (opts.secret())
formatKey(settings.getPrivateKey(), "PRIVATE");
}
}
private void formatKey(byte[] data, String type) throws UnknownHostException {
String email = settings.getEmail();
if (email == null)
email = "<no email set>";
email += " " + InetAddress.getLocalHost().getHostName();
StringBuilder sb = new StringBuilder(Base64.encodeBase64(data));
int r = 60;
while (r < sb.length()) {
sb.insert(r, "\n");
r += 60;
}
out.format("-----BEGIN %s %s KEY-----%n", email, type);
out.append(sb.toString()).append("\n");
out.format("-----END %s %s KEY-----%n", email, type);
}
/**
* Show the tail of the log output.
*/
@Arguments(arg = "service")
@Description("Show the service log")
interface logOptions extends Options {
@Description("Shows new lines in the service log as they are written")
boolean tail();
@Description("Reset the log file for the service")
boolean clear();
}
@Description("Show the service log")
public void _log(logOptions opts) throws Exception {
String s = opts._().isEmpty() ? null : opts._().get(0);
if (s == null) {
error("No such service %s", s);
return;
}
Service service = jpm.getService(s);
if (service == null) {
error("No such service %s", s);
return;
}
ServiceData data = service.getServiceData();
File logFile = new File(data.log);
if (!logFile.isFile()) {
error("Log file %s for service %s is not a file", logFile, s);
return;
}
if (opts.clear()) {
logFile.delete();
logFile.createNewFile();
}
RandomAccessFile raf = new RandomAccessFile(logFile, "r");
try {
long start = Math.max(logFile.length() - 2000, 0);
while (true) {
long l = raf.length();
byte[] buffer = new byte[(int) (l - start)];
raf.seek(start);
raf.read(buffer);
out.write(buffer);
start = l;
if (!service.isRunning() || !opts.tail())
return;
if (l == raf.length())
Thread.sleep(100);
}
}
finally {
raf.close();
}
}
/**
* Install JPM as a platform daemon that will start the services marked with
* after xxx (where boot is the canonical start).
*/
@Arguments(arg = {})
interface registerOptions extends Options {
@Description("Register for user login only")
boolean user();
}
@Description("Install JPM as a platform daemon")
public void _register(registerOptions opts) throws Exception {
jpm.register(opts.user());
}
/**
* Handle the global settings
*/
@Description("Manage user settings of jpm (in ~/.jpm). Without argument, print the current settings. "
+ "Can alse be used to create change a settings with \"jpm settings <key>=<value>\"")
interface settingOptions extends Options {
boolean clear();
boolean publicKey();
boolean secretKey();
boolean id();
boolean mac();
boolean hex();
}
@Description("Manage user settings of jpm (in ~/.jpm)")
public void _settings(settingOptions opts) throws Exception {
try {
trace("settings %s", opts.clear());
List<String> rest = opts._();
if (opts.clear()) {
settings.clear();
trace("clear %s", settings.entrySet());
}
if (opts.publicKey()) {
out.println(tos(opts.hex(), settings.getPublicKey()));
return;
}
if (opts.secretKey()) {
out.println(tos(opts.hex(), settings.getPrivateKey()));
return;
}
if (opts.id()) {
out.printf("%s\n", tos(opts.hex(), settings.getPublicKey()));
}
if (opts.mac()) {
for (String s : rest) {
byte[] data = s.getBytes("UTF-8");
byte[] signature = settings.sign(data);
out.printf("%s\n", tos(opts.hex(), signature));
}
return;
}
if (rest.isEmpty()) {
list(null, settings);
} else {
boolean set = false;
for (String s : rest) {
Matcher m = ASSIGNMENT.matcher(s);
trace("try %s", s);
if (m.matches()) {
trace("matches %s %s %s", s, m.group(1), m.group(2));
String key = m.group(1);
Instructions instr = new Instructions(key);
Collection<String> select = instr.select(settings.keySet(), true);
String value = m.group(2);
if (value == null) {
trace("list wildcard " + instr + " " + select + " " + settings.keySet());
list(select, settings);
} else {
trace("assignment ");
settings.put(key, value);
set = true;
}
} else {
err.printf("Cannot assign %s\n", s);
}
}
if (set) {
trace("saving");
settings.save();
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
private String tos(boolean hex, byte[] data) {
return hex ? Hex.toHexString(data) : Base64.encodeBase64(data);
}
private void list(Collection<String> keys, Map<String,String> map) {
for (Entry<String,String> e : map.entrySet()) {
if (keys == null || keys.contains(e.getKey()))
out.printf("%-40s = %s\n", e.getKey(), e.getValue());
}
}
/**
* Turned out that StartSSL HTTPS certificates are not recognized by the
* Java certificate store. So we have a command to get the certificate chain
*/
@Arguments(arg = "host")
@Description("Provides a way to let Java trust a host for HTTPS access. "
+ "The certificate command makes it possible to import an HTTPS certificate from a "
+ "remote host that runs HTTPS. It will contact the host over the given host and port and "
+ "then add the certificate chain's top certificate to the local keystore of the running VM. This command"
+ "requires running as an administrator/root. By default this command will only show what it"
+ "will do, specify -i/--install to really install it. Note that running this command is global for the Java VM and is persistent.")
interface CertificateOptions extends Options {
@Description("Only install the certificate when this option is specified")
boolean install();
@Description("Override the default HTTPS port 443")
int port();
@Description("Password for the keystore. Only necessary when the password has been changed before (the default is 'changeit' for MacOS and 'changeme' for others)")
String secret();
@Description("Override the default $JAVA_HOME/lib/security/(jsse)cacerts file location.")
File cacerts();
@Description("Delete the given name")
boolean delete();
}
@Description("Install a certificate that is trusted by this VM (persistent for the JVM!)")
public void _certificate(CertificateOptions opts) throws Exception {
if (!this.jpm.hasAccess())
error("Must be administrator");
if (opts.delete())
InstallCert.deleteCert(opts._().get(0), opts.secret() == null ? jpm.getPlatform().defaultCacertsPassword()
: opts.secret(), opts.cacerts());
else
InstallCert.installCert(this, opts._().get(0), opts.port() == 0 ? 443 : opts.port(),
opts.secret() == null ? jpm.getPlatform().defaultCacertsPassword() : opts.secret(), opts.cacerts(),
opts.install());
}
// void print(Iterable<RevisionRef> revisions) {
// for (RevisionRef r : revisions) {
// out.printf("%-40s %s %s\n", jpm.getCoordinates(r),
// Hex.toHexString(r._id), (r.description == null ? ""
// : r.description));
// }
// }
void printPrograms(Iterable< ? extends Program> programs) {
Justif j = new Justif(120, 40, 42, 100);
StringBuilder sb = new StringBuilder();
Formatter f = new Formatter(sb);
try {
for (Program p : programs) {
if (p.groupId.equals(Library.OSGI_GROUP) || p.groupId.equals(Library.SHA_GROUP))
f.format("%s", p.artifactId);
else
f.format("%s:%s", p.groupId, p.artifactId);
f.format("\t0-\t1");
if (p.wiki != null && p.wiki.text != null)
sb.append(p.wiki.text.replace('\n', '\f'));
else if (p.last != null) {
if (p.last.description != null)
sb.append(p.last.description.replace('\n', '\f'));
}
f.format("%n");
}
j.wrap(sb);
out.println(sb);
}
finally {
f.close();
}
}
interface RevisionPrintOptions {
@Description("Just show the coordinate only")
boolean coordinate();
@Description("Include the description field")
boolean description();
}
void printRevisions(Iterable< ? extends Revision> revisions, RevisionPrintOptions po) {
if (po.coordinate()) {
for (Revision r : revisions) {
out.println(new Coordinate(r));
}
return;
}
Justif j = new Justif(140, 40, 70, 82, 100, 120);
Formatter f = j.formatter();
try {
for (Revision r : revisions) {
f.format("[%s] ", r.phase.getIdentifier());
if (r.groupId.equals(Library.OSGI_GROUP) || r.groupId.equals(Library.SHA_GROUP))
f.format("%s ", r.artifactId);
else
f.format("%s:%s ", r.groupId, r.artifactId);
f.format("\t0%s\t1%tF\t2%s", r.version, new Date(r.modified), Hex.toHexString(r._id));
if (po.description() && r.description != null) {
f.format("\n \t1%s", r.description);
}
f.format("\n");
}
out.println(j.wrap());
}
finally {
f.close();
}
}
@Description("Find programs and libraries corresponding to the given query")
interface findOptions extends Options {
/**
* Number of search items to skip
*
* @return
*/
@Description("Number of programs to skip")
int skip();
@Description("Maximum number of programs per listing")
int limit();
}
@Description("Find programs and libraries corresponding to the given query")
public void _find(findOptions opts) throws Exception {
int skip = 0;
int limit = 0;
if (opts.skip() > 0)
skip = opts.skip();
if (opts.limit() > 0)
limit = opts.limit();
String q = new ExtList<String>(opts._()).join(" ");
List<Program> programs = jpm.find(q, skip, limit);
printPrograms(programs);
}
/**
* Some window specific commands
*/
// enum Key {
// // HKEY_LOCAL_MACHINE(WinRegistry.HKEY_LOCAL_MACHINE),
// HKEY_CURRENT_USER(WinRegistry.HKEY_CURRENT_USER);
//
// int n;
//
// Key(int n) {
// this.n = n;
// }
//
// public int value() {
// return n;
// }
// }
//
// @Arguments(arg = {
// "key", "[property]"
// })
// interface winregOptions extends Options {
// boolean localMachine();
// boolean thirtytwo();
// }
//
// @Description("Windows specific access to the registry")
// public void _winreg(winregOptions opts) throws Exception {
// List<String> _ = opts._();
// String key = _.remove(0);
// key = key.replace('/', '\\');
// String property = _.isEmpty() ? null : _.remove(0);
//
// RegistryKey environment =
// RegistryKey.HKEY_CURRENT_USER.getSubKey("Environment");
// System.out.println(environment.exists());
// System.out.println(environment.getString("Path"));
//
// //
// //
// // int n = opts.localMachine() ? WinRegistry.HKEY_LOCAL_MACHINE :
// WinRegistry.HKEY_CURRENT_USER;
// // //int wow = opts.thirtytwo() ? WinRegistry.KEY_WOW64_32KEY :
// WinRegistry.KEY_WOW64_64KEY;
// //
// // List<String> keys = WinRegistry.readStringSubKeys(n, key);
// // if (property == null) {
// // Map<String,String> map = WinRegistry.readStringValues(n, key);
// // out.println(map);
// // } else {
// // WinRegistry.readString(n, key, property);
// // }
// }
/**
* Deposit a file in JPM and scan it.
*/
@Arguments(arg = "file")
@Description("Deposit a file in a private depository")
interface DepositOptions extends Options {
@Description("If this file should be scanned")
boolean scan();
@Description("Import message for scanning")
String message();
@Description("Repository path")
String path();
@Description("Override email")
String email();
}
@Description("Deposit a file in a private depository")
public void _deposit(DepositOptions options) {
File f = IO.getFile(base, options._().get(0));
if (f.isFile()) {
error("No such file %s", f);
}
}
/**
* Alternative for command -r {@code <commandName>}
*/
@Arguments(arg = {
"command|service", "..."
})
@Description("Remove the specified command(s) or service(s) from the system")
interface UninstallOptions extends Options {}
@Description("Remove a command or a service from the system")
public void _remove(UninstallOptions opts) throws Exception {
if (!jpm.hasAccess()) {
error("No write acces, might require administrator or root privileges (sudo in *nix)");
return;
}
ArrayList<String> toDelete = new ArrayList<String>();
ArrayList<String> names = new ArrayList<String>();
List<CommandData> commands = jpm.getCommands();
for (CommandData command : commands) {
names.add(command.name);
}
List<ServiceData> services = jpm.getServices();
for (ServiceData service : services) {
names.add(service.name);
}
for (String pattern : opts._()) {
Glob glob = new Glob(pattern);
for (String name : names) {
if (glob.matcher(name).matches()) {
toDelete.add(name);
}
}
}
int ccount = 0, scount = 0;
for (String name : toDelete) {
Service s = null;
if (jpm.getCommand(name) != null) { // Try command first
trace("Corresponding command found, removing");
jpm.deleteCommand(name);
ccount++;
} else if ((s = jpm.getService(name)) != null) { // No command
// matching, try
// service
trace("Corresponding service found, removing");
s.remove();
scount++;
} else { // No match amongst commands & services
error("No matching command or service found for: %s", name);
}
}
out.format("%d command(s) removed and %d service(s) removed%n", ccount, scount);
}
@Arguments(arg = "markdown|bash-completion")
@Description("Print additional files for jpm (markdown documentation or bash completion file) to the standard output.")
interface GenerateOptions extends Options {}
@Description("Generate additional files for jpm")
public void _generate(GenerateOptions opts) throws Exception {
if (opts._().isEmpty()) {
error("Syntax: jpm generate <markdown|bash-completion>");
return;
}
String genType = opts._().get(0);
if (genType.equalsIgnoreCase("markdown")) {
IO.copy(this.getClass().getResourceAsStream("/static/jpm_prefix.md"), out);
CommandLine cl = new CommandLine(this);
cl.generateDocumentation(this, out);
} else if (genType.equalsIgnoreCase("bash-completion")) {
jpm.getPlatform().parseCompletion(this, out);
// IO.copy(this.getClass().getResourceAsStream("/aQute/jpm/platform/unix/jpm-completion.bash"),
// out);
} else {
error("Syntax: jpm generate <markdown|bash-completion>");
return;
}
}
/**
* Constructor for testing purposes
*/
public Main(JustAnotherPackageManager jpm) throws UnsupportedEncodingException {
this();
this.jpm = jpm;
}
/**
* Get an artifact
*/
@Arguments(arg = {
"coordinate"
})
@Description("Get an artifact")
interface GetOptions extends Options {
@Description("Specify an output file")
String output();
}
public void _get(GetOptions options) throws Exception {
String coord = options._().get(0);
String f = options.output();
if (f == null)
IO.copy(new URL(coord).openStream(), System.out);
else {
IO.copy(new URL(coord).openStream(), IO.getFile(f));
}
}
@Arguments(arg = {
"jar file | url", "..."
})
@Description("Provide information about a jar file")
interface WhatOptions extends Options {
@Description("Force one liner description for one jar")
boolean shortinfo();
@Description("Force long information for multiple jars")
boolean longinfo();
}
@Description("Provide information about a jar file")
public void _what(WhatOptions opts) throws Exception {
if (opts._().size() == 0) {
error("Syntax: jpm what <jar file | url>");
return;
}
String res;
if (opts._().size() == 1) {
res = jpm.what(opts._().get(0), opts.shortinfo());
if (res != null) {
out.println(res);
} else {
out.println("No information found for this file");
}
} else {
ArrayList<String> fails = new ArrayList<String>();
for (String key : opts._()) {
res = jpm.what(key, !opts.longinfo());
if (res != null) {
out.println(res);
} else {
fails.add(key);
}
}
if (fails.size() > 0) {
out.println("No information found for:");
for (String fail : fails) {
out.println(" - " + fail);
}
}
}
}
@Description("Perform updates for installed commands and services. "
+ "Without argument (and without the --all flag), list possible updates for all installed commands and services. "
+ "With arguments, apply possible updates for the specified command(s) and/or service(s).")
@Arguments(arg = {
"[command|service]", "..."
})
interface UpdateOptions extends Options {
@Description("Apply all possible updates")
boolean all();
@Description("Include staging versions for updates")
boolean staged();
}
@Description("Perform updates for installed commands and services")
public void _update(UpdateOptions opts) throws Exception {
if (!jpm.hasAccess()) {
error("No write acces, might require administrator or root privileges (sudo in *nix)");
return;
}
ArrayList<String> refs = new ArrayList<String>();
for (CommandData data : jpm.getCommands()) {
refs.add(data.name);
}
for (ServiceData data : jpm.getServices()) {
refs.add(data.name);
}
ArrayList<UpdateMemo> notFound = new ArrayList<JustAnotherPackageManager.UpdateMemo>();
ArrayList<UpdateMemo> upToDate = new ArrayList<JustAnotherPackageManager.UpdateMemo>();
ArrayList<UpdateMemo> toUpdate = new ArrayList<JustAnotherPackageManager.UpdateMemo>();
ArrayList<CommandData> datas = new ArrayList<CommandData>();
if (opts._().size() == 0) {
datas.addAll(jpm.getCommands());
datas.addAll(jpm.getServices());
} else {
for (String pattern : opts._()) {
Glob glob = new Glob(pattern);
for (String name : refs) {
if (glob.matcher(name).matches()) {
CommandData data = jpm.getCommand(name);
if (data == null) {
Service service = jpm.getService(name);
if (service != null) {
data = service.getServiceData();
}
}
if (data != null) {
datas.add(data);
}
}
}
}
}
for (CommandData data : datas) {
jpm.listUpdates(notFound, upToDate, toUpdate, data, opts.staged());
}
if (opts.all() || opts._().size() > 0) {
for (UpdateMemo memo : toUpdate) {
jpm.update(memo);
}
out.format("%d command(s) updated%n", toUpdate.size());
} else {
Justif justif = new Justif(100, 20, 50);
StringBuilder sb = new StringBuilder();
Formatter f = new Formatter(sb);
if (upToDate.size() > 0) {
f.format("Up to date:%n");
for (UpdateMemo memo : upToDate) {
if (memo.current instanceof ServiceData) {
f.format(" - %s (service) \t0- %s%n", memo.current.name, memo.current.version);
} else {
f.format(" - %s \t0- %s%n", memo.current.name, memo.current.version);
}
}
f.format("%n");
}
if (toUpdate.size() > 0) {
f.format("Update available:%n");
for (UpdateMemo memo : toUpdate) {
if (memo.current instanceof ServiceData) {
f.format(" - %s (service) \t0- %s \t1-> %s%n", memo.current.name, memo.current.version,
memo.best.version);
} else {
f.format(" - %s \t0- %s \t1-> %s%n", memo.current.name, memo.current.version, memo.best.version);
}
}
f.format("%n");
}
if (notFound.size() > 0) {
if (opts.staged()) {
f.format("Information not found (local install ?):%n");
} else {
f.format("Information not found (try including staging versions with the --staged (-s) flag)%n");
}
for (UpdateMemo memo : notFound) {
if (memo.current instanceof ServiceData) {
f.format(" - %s (service)%n", memo.current.name);
} else {
f.format(" - %s%n", memo.current.name);
}
}
}
if (toUpdate.size() > 0) {
f.format("%nIn order to apply all possible updates, run jpm update again with the --all (or -a) flag.%n");
}
f.flush();
justif.wrap(sb);
out.println(sb.toString());
f.close();
}
}
/**
* Show a list of candidates from a coordinate
*/
@Arguments(arg = "coordinate")
@Description("Print out the candidates from a coordinate specification. A coordinate is:\n\n"
+ " coordinate \t0:\t1[groupId ':'] artifactId \n\t1[ '@' [ version ] ( '*' | '=' | '~' | '!')]\n"
+ " '*' \t0:\t1Version, if specified, is treated as required prefix of the actual version. Sees MASTER | STAGING | LOCKED\n"
+ " '=' \t0:\t1Version, if specified, must match exactly. Sees MASTER\n"
+ " '~' \t0:\t1Version, if specified, is treated as required prefix of the actual version. Sees all phases\n"
+ " '!' \t0:\t1Version, if specified, is treated as required prefix of the actual version. Sees normally invisible phases")
interface CandidateOptions extends Options, RevisionPrintOptions {
}
@Description("List the candidates for a coordinate")
public void _candidates(CandidateOptions options) throws Exception {
String c = options._().get(0);
if (!Coordinate.COORDINATE_P.matcher(c).matches()) {
error("Not a proper coordinate %s", c);
return;
}
printRevisions(jpm.getCandidates(new Coordinate(c)), options);
}
/**
* Start jpm as daemon
*
* @throws Exception
*/
public void _daemon(Options opts) throws Exception {
jpm.daemon();
}
@Arguments(arg = {})
interface UseOptions extends Options {
@Description("Forget the current settings.")
boolean forget();
@Description("Save settings.")
boolean save();
}
@Description("Manage the current setting of the home directory and the bin directory. These settings can be set with the initial options -g/--global, -u/--user, and -h/--home")
public void _use(UseOptions o) {
out.println("Home dir " + jpm.getHomeDir());
out.println("Bin dir " + jpm.getBinDir());
if (o.forget()) {
settings.remove(JPM_CONFIG_BIN);
settings.remove(JPM_CONFIG_HOME);
settings.save();
} else if (o.save()) {
if (!jpm.getHomeDir().isDirectory()) {
error("The current home directory is not a JPM directory, init to initialize it, this will save the permanent settings to use that directory by default");
return;
}
settings.put(JPM_CONFIG_BIN, jpm.getBinDir().getAbsolutePath());
settings.put(JPM_CONFIG_HOME, jpm.getHomeDir().getAbsolutePath());
settings.save();
}
}
/**
* Run the platform init
*/
public void __postinstall(Options opts) {
jpm.doPostInstall();
}
}