/***** BEGIN LICENSE BLOCK *****
* Version: CPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Common Public
* License Version 1.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.eclipse.org/legal/cpl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the CPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the CPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.jruby.ast.executable.Script;
import org.jruby.exceptions.MainExitException;
import org.jruby.runtime.Constants;
import org.jruby.runtime.load.LoadService;
import org.jruby.util.ClassCache;
import org.jruby.util.JRubyFile;
import org.jruby.util.KCode;
import org.jruby.util.NormalizedFile;
import org.jruby.util.SafePropertyAccessor;
import org.objectweb.asm.Opcodes;
public class RubyInstanceConfig {
/**
* The max count of active methods eligible for JIT-compilation.
*/
private static final int JIT_MAX_METHODS_LIMIT = 4096;
/**
* The max size of JIT-compiled methods (full class size) allowed.
*/
private static final int JIT_MAX_SIZE_LIMIT = Integer.MAX_VALUE;
/**
* The JIT threshold to the specified method invocation count.
*/
private static final int JIT_THRESHOLD = 50;
/** The version to use for generated classes. Set to current JVM version by default */
public static final int JAVA_VERSION;
/**
* Default size for chained compilation.
*/
private static final int CHAINED_COMPILE_LINE_COUNT_DEFAULT = 500;
/**
* The number of lines at which a method, class, or block body is split into
* chained methods (to dodge 64k method-size limit in JVM).
*/
public static final int CHAINED_COMPILE_LINE_COUNT
= SafePropertyAccessor.getInt("jruby.compile.chainsize", CHAINED_COMPILE_LINE_COUNT_DEFAULT);
public enum CompileMode {
JIT, FORCE, OFF;
public boolean shouldPrecompileCLI() {
switch (this) {
case JIT: case FORCE:
return true;
}
return false;
}
public boolean shouldJIT() {
switch (this) {
case JIT: case FORCE:
return true;
}
return false;
}
public boolean shouldPrecompileAll() {
return this == FORCE;
}
}
private InputStream input = System.in;
private PrintStream output = System.out;
private PrintStream error = System.err;
private Profile profile = Profile.DEFAULT;
private boolean objectSpaceEnabled
= SafePropertyAccessor.getBoolean("jruby.objectspace.enabled", false);
private CompileMode compileMode = CompileMode.JIT;
private boolean runRubyInProcess = true;
private String currentDirectory;
private Map environment;
private String[] argv = {};
private final boolean jitLogging;
private final boolean jitLoggingVerbose;
private final int jitLogEvery;
private final int jitThreshold;
private final int jitMax;
private final int jitMaxSize;
private final boolean samplingEnabled;
private CompatVersion compatVersion;
private ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
private ClassLoader loader = contextLoader == null ? RubyInstanceConfig.class.getClassLoader() : contextLoader;
private ClassCache<Script> classCache;
// from CommandlineParser
private List<String> loadPaths = new ArrayList<String>();
private Set<String> excludedMethods = new HashSet<String>();
private StringBuffer inlineScript = new StringBuffer();
private boolean hasInlineScript = false;
private String scriptFileName = null;
private List<String> requiredLibraries = new ArrayList<String>();
private boolean benchmarking = false;
private boolean argvGlobalsOn = false;
private boolean assumeLoop = false;
private boolean assumePrinting = false;
private Map optionGlobals = new HashMap();
private boolean processLineEnds = false;
private boolean split = false;
// This property is a Boolean, to allow three values, so it can match MRI's nil, false and true
private Boolean verbose = Boolean.FALSE;
private boolean debug = false;
private boolean showVersion = false;
private boolean showBytecode = false;
private boolean showCopyright = false;
private boolean endOfArguments = false;
private boolean shouldRunInterpreter = true;
private boolean shouldPrintUsage = false;
private boolean shouldPrintProperties=false;
private boolean yarv = false;
private boolean rubinius = false;
private boolean yarvCompile = false;
private KCode kcode = KCode.NONE;
private String recordSeparator = "\n";
private boolean shouldCheckSyntax = false;
private String inputFieldSeparator = null;
private boolean managementEnabled = true;
private int safeLevel = 0;
private String jrubyHome;
public static final boolean FASTEST_COMPILE_ENABLED
= SafePropertyAccessor.getBoolean("jruby.compile.fastest");
public static final boolean BOXED_COMPILE_ENABLED
= FASTEST_COMPILE_ENABLED
|| SafePropertyAccessor.getBoolean("jruby.compile.boxed");
public static final boolean FASTOPS_COMPILE_ENABLED
= FASTEST_COMPILE_ENABLED
|| SafePropertyAccessor.getBoolean("jruby.compile.fastops");
public static final boolean FRAMELESS_COMPILE_ENABLED
= FASTEST_COMPILE_ENABLED
|| SafePropertyAccessor.getBoolean("jruby.compile.frameless");
public static final boolean POSITIONLESS_COMPILE_ENABLED
= FASTEST_COMPILE_ENABLED
|| SafePropertyAccessor.getBoolean("jruby.compile.positionless");
public static final boolean THREADLESS_COMPILE_ENABLED
= FASTEST_COMPILE_ENABLED
|| SafePropertyAccessor.getBoolean("jruby.compile.threadless");
public static final boolean FASTCASE_COMPILE_ENABLED =
SafePropertyAccessor.getBoolean("jruby.compile.fastcase");
public static final boolean LAZYHANDLES_COMPILE = SafePropertyAccessor.getBoolean("jruby.compile.lazyHandles", false);
public static final boolean FORK_ENABLED
= SafePropertyAccessor.getBoolean("jruby.fork.enabled");
public static final boolean POOLING_ENABLED
= SafePropertyAccessor.getBoolean("jruby.thread.pool.enabled");
public static final int POOL_MAX
= SafePropertyAccessor.getInt("jruby.thread.pool.max", Integer.MAX_VALUE);
public static final int POOL_MIN
= SafePropertyAccessor.getInt("jruby.thread.pool.min", 0);
public static final int POOL_TTL
= SafePropertyAccessor.getInt("jruby.thread.pool.ttl", 60);
public static final boolean NATIVE_NET_PROTOCOL
= SafePropertyAccessor.getBoolean("jruby.native.net.protocol", false);
public static boolean FULL_TRACE_ENABLED
= SafePropertyAccessor.getBoolean("jruby.debug.fullTrace", false);
public static final String COMPILE_EXCLUDE
= SafePropertyAccessor.getProperty("jruby.jit.exclude");
public static boolean nativeEnabled = true;
public static interface LoadServiceCreator {
LoadService create(Ruby runtime);
LoadServiceCreator DEFAULT = new LoadServiceCreator() {
public LoadService create(Ruby runtime) {
return new LoadService(runtime);
}
};
}
private LoadServiceCreator creator = LoadServiceCreator.DEFAULT;
static {
String specVersion = null;
try {
specVersion = System.getProperty("jruby.bytecode.version");
if (specVersion == null) {
specVersion = System.getProperty("java.specification.version");
}
if (System.getProperty("jruby.native.enabled") != null) {
nativeEnabled = Boolean.getBoolean("jruby.native.enabled");
}
} catch (SecurityException se) {
nativeEnabled = false;
specVersion = "1.5";
}
if (specVersion.equals("1.5")) {
JAVA_VERSION = Opcodes.V1_5;
} else {
JAVA_VERSION = Opcodes.V1_6;
}
}
public int characterIndex = 0;
public RubyInstanceConfig() {
if (Ruby.isSecurityRestricted())
currentDirectory = "/";
else {
currentDirectory = JRubyFile.getFileProperty("user.dir");
}
samplingEnabled = SafePropertyAccessor.getBoolean("jruby.sampling.enabled", false);
String compatString = SafePropertyAccessor.getProperty("jruby.compat.version", "RUBY1_8");
if (compatString.equalsIgnoreCase("RUBY1_8")) {
compatVersion = CompatVersion.RUBY1_8;
} else if (compatString.equalsIgnoreCase("RUBY1_9")) {
compatVersion = CompatVersion.RUBY1_9;
} else {
System.err.println("Compatibility version `" + compatString + "' invalid; use RUBY1_8 or RUBY1_9. Using RUBY1_8.");
compatVersion = CompatVersion.RUBY1_8;
}
if (Ruby.isSecurityRestricted()) {
compileMode = CompileMode.OFF;
jitLogging = false;
jitLoggingVerbose = false;
jitLogEvery = 0;
jitThreshold = -1;
jitMax = 0;
jitMaxSize = -1;
managementEnabled = false;
} else {
String threshold = SafePropertyAccessor.getProperty("jruby.jit.threshold");
String max = SafePropertyAccessor.getProperty("jruby.jit.max");
String maxSize = SafePropertyAccessor.getProperty("jruby.jit.maxsize");
if (COMPILE_EXCLUDE != null) {
String[] elements = COMPILE_EXCLUDE.split(",");
for (String element : elements) excludedMethods.add(element);
}
managementEnabled = SafePropertyAccessor.getBoolean("jruby.management.enabled", true);
runRubyInProcess = SafePropertyAccessor.getBoolean("jruby.launch.inproc", true);
boolean jitProperty = SafePropertyAccessor.getProperty("jruby.jit.enabled") != null;
if (jitProperty) {
error.print("jruby.jit.enabled property is deprecated; use jruby.compile.mode=(OFF|JIT|FORCE) for -C, default, and +C flags");
compileMode = SafePropertyAccessor.getBoolean("jruby.jit.enabled") ? CompileMode.JIT : CompileMode.OFF;
} else {
String jitModeProperty = SafePropertyAccessor.getProperty("jruby.compile.mode", "JIT");
if (jitModeProperty.equals("OFF")) {
compileMode = CompileMode.OFF;
} else if (jitModeProperty.equals("JIT")) {
compileMode = CompileMode.JIT;
} else if (jitModeProperty.equals("FORCE")) {
compileMode = CompileMode.FORCE;
} else {
error.print("jruby.compile.mode property must be OFF, JIT, FORCE, or unset; defaulting to JIT");
compileMode = CompileMode.JIT;
}
}
jitLogging = SafePropertyAccessor.getBoolean("jruby.jit.logging");
jitLoggingVerbose = SafePropertyAccessor.getBoolean("jruby.jit.logging.verbose");
String logEvery = SafePropertyAccessor.getProperty("jruby.jit.logEvery");
jitLogEvery = logEvery == null ? 0 : Integer.parseInt(logEvery);
jitThreshold = threshold == null ?
JIT_THRESHOLD : Integer.parseInt(threshold);
jitMax = max == null ?
JIT_MAX_METHODS_LIMIT : Integer.parseInt(max);
jitMaxSize = maxSize == null ?
JIT_MAX_SIZE_LIMIT : Integer.parseInt(maxSize);
}
// default ClassCache using jitMax as a soft upper bound
classCache = new ClassCache<Script>(loader, jitMax);
if (FORK_ENABLED) {
error.print("WARNING: fork is highly unlikely to be safe or stable on the JVM. Have fun!\n");
}
}
public LoadServiceCreator getLoadServiceCreator() {
return creator;
}
public void setLoadServiceCreator(LoadServiceCreator creator) {
this.creator = creator;
}
public LoadService createLoadService(Ruby runtime) {
return this.creator.create(runtime);
}
public String getBasicUsageHelp() {
StringBuilder sb = new StringBuilder();
sb
.append("Usage: jruby [switches] [--] [programfile] [arguments]\n")
.append(" -0[octal] specify record separator (\0, if no argument)\n")
.append(" -a autosplit mode with -n or -p (splits $_ into $F)\n")
.append(" -b benchmark mode, times the script execution\n")
.append(" -c check syntax only\n")
.append(" -Cdirectory cd to directory, before executing your script\n")
.append(" -d set debugging flags (set $DEBUG to true)\n")
.append(" -e 'command' one line of script. Several -e's allowed. Omit [programfile]\n")
.append(" -Fpattern split() pattern for autosplit (-a)\n")
//.append(" -i[extension] edit ARGV files in place (make backup if extension supplied)\n")
.append(" -Idirectory specify $LOAD_PATH directory (may be used more than once)\n")
.append(" -J[java option] pass an option on to the JVM (e.g. -J-Xmx512m)\n")
.append(" use --properties to list JRuby properties\n")
.append(" run 'java -help' for a list of other Java options\n")
.append(" -Kkcode specifies code-set (e.g. -Ku for Unicode\n")
.append(" -l enable line ending processing\n")
.append(" -n assume 'while gets(); ... end' loop around your script\n")
.append(" -p assume loop like -n but print line also like sed\n")
.append(" -rlibrary require the library, before executing your script\n")
.append(" -s enable some switch parsing for switches after script name\n")
.append(" -S look for the script in bin or using PATH environment variable\n")
.append(" -T[level] turn on tainting checks\n")
.append(" -v print version number, then turn on verbose mode\n")
.append(" -w turn warnings on for your script\n")
.append(" -W[level] set warning level; 0=silence, 1=medium, 2=verbose (default)\n")
//.append(" -x[directory] strip off text before #!ruby line and perhaps cd to directory\n")
.append(" -X[option] enable extended option (omit option to list)\n")
.append(" --copyright print the copyright\n")
.append(" --debug sets the execution mode most suitable for debugger functionality\n")
.append(" --jdb runs JRuby process under JDB\n")
.append(" --properties List all configuration Java properties (pass -J-Dproperty=value)\n")
.append(" --sample run with profiling using the JVM's sampling profiler\n")
.append(" --client use the non-optimizing \"client\" JVM (improves startup; default)\n")
.append(" --server use the optimizing \"server\" JVM (improves perf)\n")
.append(" --manage enable remote JMX management and monitoring of the VM and JRuby\n")
.append(" --headless do not launch a GUI window, no matter what\n")
.append(" --1.8 specify Ruby 1.8.x compatibility (default)\n")
.append(" --1.9 specify Ruby 1.9.x compatibility\n")
.append(" --bytecode show the JVM bytecode produced by compiling specified code\n")
.append(" --version print the version\n");
return sb.toString();
}
public String getExtendedHelp() {
StringBuilder sb = new StringBuilder();
sb
.append("These flags are for extended JRuby options.\n")
.append("Specify them by passing -X<option>\n")
.append(" -O run with ObjectSpace disabled (default; improves performance)\n")
.append(" +O run with ObjectSpace enabled (reduces performance)\n")
.append(" -C disable all compilation\n")
.append(" +C force compilation of all scripts before they are run (except eval)\n")
.append(" -y read a YARV-compiled Ruby script and run that (EXPERIMENTAL)\n")
.append(" -Y compile a Ruby script into YARV bytecodes and run this (EXPERIMENTAL)\n")
.append(" -R read a Rubinius-compiled Ruby script and run that (EXPERIMENTAL)\n");
return sb.toString();
}
public String getPropertyHelp() {
StringBuilder sb = new StringBuilder();
sb
.append("These properties can be used to alter runtime behavior for perf or compatibility.\n")
.append("Specify them by passing -J-D<property>=<value>\n")
.append("\nCOMPILER SETTINGS:\n")
.append(" jruby.compile.mode=JIT|FORCE|OFF\n")
.append(" Set compilation mode. JIT is default; FORCE compiles all, OFF disables\n")
.append(" jruby.compile.fastest=true|false\n")
.append(" (EXPERIMENTAL) Turn on all experimental compiler optimizations\n")
.append(" jruby.compile.boxed=true|false\n")
.append(" (EXPERIMENTAL) Use boxed variables; this can speed up some methods. Default is false\n")
.append(" jruby.compile.frameless=true|false\n")
.append(" (EXPERIMENTAL) Turn on frameless compilation where possible\n")
.append(" jruby.compile.positionless=true|false\n")
.append(" (EXPERIMENTAL) Turn on compilation that avoids updating Ruby position info. Default is false\n")
.append(" jruby.compile.threadless=true|false\n")
.append(" (EXPERIMENTAL) Turn on compilation without polling for \"unsafe\" thread events. Default is false\n")
.append(" jruby.compile.fastops=true|false\n")
.append(" (EXPERIMENTAL) Turn on fast operators for Fixnum. Default is false\n")
.append(" jruby.compile.fastcase=true|false\n")
.append(" (EXPERIMENTAL) Turn on fast case/when for all-Fixnum whens. Default is false\n")
.append(" jruby.compile.chainsize=<line count>\n")
.append(" Set the number of lines at which compiled bodies are \"chained\". Default is " + CHAINED_COMPILE_LINE_COUNT_DEFAULT + "\n")
.append(" jruby.compile.lazyHandles=true|false\n")
.append(" Generate method bindings (handles) for compiled methods lazily. Default is false.\n")
.append("\nJIT SETTINGS:\n")
.append(" jruby.jit.threshold=<invocation count>\n")
.append(" Set the JIT threshold to the specified method invocation count. Default is " + JIT_THRESHOLD + ".\n")
.append(" jruby.jit.max=<method count>\n")
.append(" Set the max count of active methods eligible for JIT-compilation.\n")
.append(" Default is " + JIT_MAX_METHODS_LIMIT + " per runtime. A value of 0 disables JIT, -1 disables max.\n")
.append(" jruby.jit.maxsize=<jitted method size (full .class)>\n")
.append(" Set the maximum full-class byte size allowed for jitted methods. Default is Integer.MAX_VALUE\n")
.append(" jruby.jit.logging=true|false\n")
.append(" Enable JIT logging (reports successful compilation). Default is false\n")
.append(" jruby.jit.logging.verbose=true|false\n")
.append(" Enable verbose JIT logging (reports failed compilation). Default is false\n")
.append(" jruby.jit.logEvery=<method count>\n")
.append(" Log a message every n methods JIT compiled. Default is 0 (off).\n")
.append(" jruby.jit.exclude=<ClsOrMod,ClsOrMod::method_name,-::method_name>\n")
.append(" Exclude methods from JIT by class/module short name, c/m::method_name,\n")
.append(" or -::method_name for anon/singleton classes/modules. Comma-delimited.\n")
.append("\nNATIVE SUPPORT:\n")
.append(" jruby.native.enabled=true|false\n")
.append(" Enable/disable native extensions (like JNA for non-Java APIs; Default is true\n")
.append(" (This affects all JRuby instances in a given JVM)\n")
.append(" jruby.native.verbose=true|false\n")
.append(" Enable verbose logging of native extension loading. Default is false.\n")
.append(" jruby.fork.enabled=true|false\n")
.append(" (EXPERIMENTAL, maybe dangerous) Enable fork(2) on platforms that support it.\n")
.append("\nTHREAD POOLING:\n")
.append(" jruby.thread.pool.enabled=true|false\n")
.append(" Enable reuse of native backing threads via a thread pool. Default is false.\n")
.append(" jruby.thread.pool.min=<min thread count>\n")
.append(" The minimum number of threads to keep alive in the pool. Default is 0.\n")
.append(" jruby.thread.pool.max=<max thread count>\n")
.append(" The maximum number of threads to allow in the pool. Default is unlimited.\n")
.append(" jruby.thread.pool.ttl=<time to live, in seconds>\n")
.append(" The maximum number of seconds to keep alive an idle thread. Default is 60.\n")
.append("\nMISCELLANY:\n")
.append(" jruby.compat.version=RUBY1_8|RUBY1_9\n")
.append(" Specify the major Ruby version to be compatible with; Default is RUBY1_8\n")
.append(" jruby.objectspace.enabled=true|false\n")
.append(" Enable or disable ObjectSpace.each_object (default is disabled)\n")
.append(" jruby.launch.inproc=true|false\n")
.append(" Set in-process launching of e.g. system('ruby ...'). Default is true\n")
.append(" jruby.bytecode.version=1.5|1.6\n")
.append(" Set bytecode version for JRuby to generate. Default is current JVM version.\n")
.append(" jruby.management.enabled=true|false\n")
.append(" Set whether JMX management is enabled. Default is true.\n")
.append(" jruby.debug.fullTrace=true|false\n")
.append(" Set whether full traces are enabled (c-call/c-return). Default is false.\n");
return sb.toString();
}
public String getVersionString() {
String ver = Constants.RUBY_VERSION;
switch (compatVersion) {
case RUBY1_8:
ver = Constants.RUBY_VERSION;
break;
case RUBY1_9:
ver = Constants.RUBY1_9_VERSION;
break;
}
String fullVersion = String.format(
"jruby %s (ruby %s patchlevel %s) (%s rev %s) [%s-java]\n",
Constants.VERSION, ver, Constants.RUBY_PATCHLEVEL,
Constants.COMPILE_DATE, Constants.REVISION,
SafePropertyAccessor.getProperty("os.arch", "unknown")
);
return fullVersion;
}
public String getCopyrightString() {
return "JRuby - Copyright (C) 2001-2008 The JRuby Community (and contribs)\n";
}
public void processArguments(String[] arguments) {
new ArgumentProcessor(arguments).processArguments();
}
public CompileMode getCompileMode() {
return compileMode;
}
public void setCompileMode(CompileMode compileMode) {
this.compileMode = compileMode;
}
public boolean isJitLogging() {
return jitLogging;
}
public boolean isJitLoggingVerbose() {
return jitLoggingVerbose;
}
public int getJitLogEvery() {
return jitLogEvery;
}
public boolean isSamplingEnabled() {
return samplingEnabled;
}
public int getJitThreshold() {
return jitThreshold;
}
public int getJitMax() {
return jitMax;
}
public int getJitMaxSize() {
return jitMaxSize;
}
public boolean isRunRubyInProcess() {
return runRubyInProcess;
}
public void setRunRubyInProcess(boolean flag) {
this.runRubyInProcess = flag;
}
public void setInput(InputStream newInput) {
input = newInput;
}
public InputStream getInput() {
return input;
}
public CompatVersion getCompatVersion() {
return compatVersion;
}
public void setOutput(PrintStream newOutput) {
output = newOutput;
}
public PrintStream getOutput() {
return output;
}
public void setError(PrintStream newError) {
error = newError;
}
public PrintStream getError() {
return error;
}
public void setCurrentDirectory(String newCurrentDirectory) {
currentDirectory = newCurrentDirectory;
}
public String getCurrentDirectory() {
return currentDirectory;
}
public void setProfile(Profile newProfile) {
profile = newProfile;
}
public Profile getProfile() {
return profile;
}
public void setObjectSpaceEnabled(boolean newObjectSpaceEnabled) {
objectSpaceEnabled = newObjectSpaceEnabled;
}
public boolean isObjectSpaceEnabled() {
return objectSpaceEnabled;
}
public void setEnvironment(Map newEnvironment) {
environment = newEnvironment;
}
public Map getEnvironment() {
return environment;
}
public ClassLoader getLoader() {
return loader;
}
public void setLoader(ClassLoader loader) {
// Setting the loader needs to reset the class cache
if(this.loader != loader) {
this.classCache = new ClassCache<Script>(loader, this.classCache.getMax());
}
this.loader = loader;
}
public String[] getArgv() {
return argv;
}
public void setArgv(String[] argv) {
this.argv = argv;
}
public String getJRubyHome() {
if (jrubyHome == null) {
if (Ruby.isSecurityRestricted()) {
return "SECURITY RESTRICTED";
}
jrubyHome = verifyHome(SafePropertyAccessor.getProperty("jruby.home",
SafePropertyAccessor.getProperty("user.home") + "/.jruby"));
try {
// This comment also in rbConfigLibrary
// Our shell scripts pass in non-canonicalized paths, but even if we didn't
// anyone who did would become unhappy because Ruby apps expect no relative
// operators in the pathname (rubygems, for example).
jrubyHome = new NormalizedFile(jrubyHome).getCanonicalPath();
} catch (IOException e) { }
jrubyHome = new NormalizedFile(jrubyHome).getAbsolutePath();
}
return jrubyHome;
}
public void setJRubyHome(String home) {
jrubyHome = verifyHome(home);
}
// We require the home directory to be absolute
private String verifyHome(String home) {
if (home.equals(".")) {
home = System.getProperty("user.dir");
}
if (!home.startsWith("file:")) {
NormalizedFile f = new NormalizedFile(home);
if (!f.isAbsolute()) {
home = f.getAbsolutePath();
}
f.mkdirs();
}
return home;
}
private class ArgumentProcessor {
private String[] arguments;
private int argumentIndex = 0;
public ArgumentProcessor(String[] arguments) {
this.arguments = arguments;
}
public void processArguments() {
while (argumentIndex < arguments.length && isInterpreterArgument(arguments[argumentIndex])) {
processArgument();
argumentIndex++;
}
if (!hasInlineScript && scriptFileName == null) {
if (argumentIndex < arguments.length) {
setScriptFileName(arguments[argumentIndex]); //consume the file name
argumentIndex++;
}
}
processArgv();
}
private void processArgv() {
List<String> arglist = new ArrayList<String>();
for (; argumentIndex < arguments.length; argumentIndex++) {
String arg = arguments[argumentIndex];
if (argvGlobalsOn && arg.startsWith("-")) {
arg = arg.substring(1);
if (arg.indexOf('=') > 0) {
String[] keyvalue = arg.split("=", 2);
optionGlobals.put(keyvalue[0], keyvalue[1]);
} else {
optionGlobals.put(arg, null);
}
} else {
argvGlobalsOn = false;
arglist.add(arg);
}
}
// Remaining arguments are for the script itself
argv = arglist.toArray(new String[arglist.size()]);
}
private boolean isInterpreterArgument(String argument) {
return (argument.charAt(0) == '-' || argument.charAt(0) == '+') && !endOfArguments;
}
private String getArgumentError(String additionalError) {
return "jruby: invalid argument\n" + additionalError + "\n";
}
private void processArgument() {
String argument = arguments[argumentIndex];
FOR : for (characterIndex = 1; characterIndex < argument.length(); characterIndex++) {
switch (argument.charAt(characterIndex)) {
case '0': {
String temp = grabOptionalValue();
if (null == temp) {
recordSeparator = "\u0000";
} else if (temp.equals("0")) {
recordSeparator = "\n\n";
} else if (temp.equals("777")) {
recordSeparator = "\uFFFF"; // Specify something that can't separate
} else {
try {
int val = Integer.parseInt(temp, 8);
recordSeparator = "" + (char) val;
} catch (Exception e) {
MainExitException mee = new MainExitException(1, getArgumentError(" -0 must be followed by either 0, 777, or a valid octal value"));
mee.setUsageError(true);
throw mee;
}
}
break FOR;
}
case 'a':
split = true;
break;
case 'b':
benchmarking = true;
break;
case 'c':
shouldCheckSyntax = true;
break;
case 'C':
try {
String saved = grabValue(getArgumentError(" -C must be followed by a directory expression"));
File base = new File(currentDirectory);
File newDir = new File(saved);
if (newDir.isAbsolute()) {
currentDirectory = newDir.getCanonicalPath();
} else {
currentDirectory = new File(base, newDir.getPath()).getCanonicalPath();
}
if (!(new File(currentDirectory).isDirectory())) {
MainExitException mee = new MainExitException(1, "jruby: Can't chdir to " + saved + " (fatal)");
mee.setUsageError(true);
throw mee;
}
} catch (IOException e) {
MainExitException mee = new MainExitException(1, getArgumentError(" -C must be followed by a valid directory"));
mee.setUsageError(true);
throw mee;
}
break;
case 'd':
debug = true;
verbose = Boolean.TRUE;
break;
case 'e':
inlineScript.append(grabValue(getArgumentError(" -e must be followed by an expression to evaluate")));
inlineScript.append('\n');
hasInlineScript = true;
break FOR;
case 'F':
inputFieldSeparator = grabValue(getArgumentError(" -F must be followed by a pattern for input field separation"));
break;
case 'h':
shouldPrintUsage = true;
shouldRunInterpreter = false;
break;
// FIXME: -i flag not supported
// case 'i' :
// break;
case 'I':
String s = grabValue(getArgumentError("-I must be followed by a directory name to add to lib path"));
String[] ls = s.split(java.io.File.pathSeparator);
for (int i = 0; i < ls.length; i++) {
loadPaths.add(ls[i]);
}
break FOR;
case 'K':
// FIXME: No argument seems to work for -K in MRI plus this should not
// siphon off additional args 'jruby -K ~/scripts/foo'. Also better error
// processing.
String eArg = grabValue(getArgumentError("provide a value for -K"));
kcode = KCode.create(null, eArg);
break;
case 'l':
processLineEnds = true;
break;
case 'n':
assumeLoop = true;
break;
case 'p':
assumePrinting = true;
assumeLoop = true;
break;
case 'r':
requiredLibraries.add(grabValue(getArgumentError("-r must be followed by a package to require")));
break FOR;
case 's' :
argvGlobalsOn = true;
break;
case 'S':
runBinScript();
break FOR;
case 'T' :{
String temp = grabOptionalValue();
int value = 1;
if(temp!=null) {
try {
value = Integer.parseInt(temp, 8);
} catch(Exception e) {
value = 1;
}
}
safeLevel = value;
break FOR;
}
case 'v':
verbose = Boolean.TRUE;
setShowVersion(true);
break;
case 'w':
verbose = Boolean.TRUE;
break;
case 'W': {
String temp = grabOptionalValue();
int value = 2;
if (null != temp) {
if (temp.equals("2")) {
value = 2;
} else if (temp.equals("1")) {
value = 1;
} else if (temp.equals("0")) {
value = 0;
} else {
MainExitException mee = new MainExitException(1, getArgumentError(" -W must be followed by either 0, 1, 2 or nothing"));
mee.setUsageError(true);
throw mee;
}
}
switch (value) {
case 0:
verbose = null;
break;
case 1:
verbose = Boolean.FALSE;
break;
case 2:
verbose = Boolean.TRUE;
break;
}
break FOR;
}
// FIXME: -x flag not supported
// case 'x' :
// break;
case 'X':
String extendedOption = grabOptionalValue();
if (extendedOption == null) {
throw new MainExitException(0, "jruby: missing extended option, listing available options\n" + getExtendedHelp());
} else if (extendedOption.equals("-O")) {
objectSpaceEnabled = false;
} else if (extendedOption.equals("+O")) {
objectSpaceEnabled = true;
} else if (extendedOption.equals("-C")) {
compileMode = CompileMode.OFF;
} else if (extendedOption.equals("+C")) {
compileMode = CompileMode.FORCE;
} else if (extendedOption.equals("-y")) {
yarv = true;
} else if (extendedOption.equals("-Y")) {
yarvCompile = true;
} else if (extendedOption.equals("-R")) {
rubinius = true;
} else {
MainExitException mee =
new MainExitException(1, "jruby: invalid extended option " + extendedOption + " (-X will list valid options)\n");
mee.setUsageError(true);
throw mee;
}
break FOR;
case '-':
if (argument.equals("--command") || argument.equals("--bin")) {
characterIndex = argument.length();
runBinScript();
break;
} else if (argument.equals("--compat")) {
characterIndex = argument.length();
compatVersion = CompatVersion.getVersionFromString(grabValue(getArgumentError("--compat must be RUBY1_8 or RUBY1_9")));
if (compatVersion == null) {
compatVersion = CompatVersion.RUBY1_8;
}
break FOR;
} else if (argument.equals("--copyright")) {
setShowCopyright(true);
shouldRunInterpreter = false;
break FOR;
} else if (argument.equals("--debug")) {
compileMode = CompileMode.OFF;
FULL_TRACE_ENABLED = true;
System.setProperty("jruby.reflection", "true");
break FOR;
} else if (argument.equals("--jdb")) {
debug = true;
verbose = Boolean.TRUE;
break;
} else if (argument.equals("--help")) {
shouldPrintUsage = true;
shouldRunInterpreter = false;
break;
} else if (argument.equals("--properties")) {
shouldPrintProperties = true;
shouldRunInterpreter = false;
break;
} else if (argument.equals("--version")) {
setShowVersion(true);
break FOR;
} else if (argument.equals("--bytecode")) {
setShowBytecode(true);
break FOR;
} else {
if (argument.equals("--")) {
// ruby interpreter compatibilty
// Usage: ruby [switches] [--] [programfile] [arguments])
endOfArguments = true;
break;
}
}
default:
throw new MainExitException(1, "jruby: unknown option " + argument);
}
}
}
private void runBinScript() {
String scriptName = grabValue("jruby: provide a bin script to execute");
if (scriptName.equals("irb")) {
scriptName = "jirb";
}
scriptFileName = scriptName;
if (!new File(scriptFileName).exists()) {
try {
String jrubyHome = JRubyFile.create(System.getProperty("user.dir"), JRubyFile.getFileProperty("jruby.home")).getCanonicalPath();
scriptFileName = JRubyFile.create(jrubyHome + JRubyFile.separator + "bin", scriptName).getCanonicalPath();
} catch (IOException io) {
MainExitException mee = new MainExitException(1, "jruby: Can't determine script filename");
mee.setUsageError(true);
throw mee;
}
}
// route 'gem' through ruby code in case we're running out of the complete jar
if (scriptName.equals("gem") || !new File(scriptFileName).exists()) {
requiredLibraries.add("jruby/commands");
inlineScript.append("JRuby::Commands." + scriptName);
inlineScript.append("\n");
hasInlineScript = true;
}
endOfArguments = true;
}
private String grabValue(String errorMessage) {
characterIndex++;
if (characterIndex < arguments[argumentIndex].length()) {
return arguments[argumentIndex].substring(characterIndex);
}
argumentIndex++;
if (argumentIndex < arguments.length) {
return arguments[argumentIndex];
}
MainExitException mee = new MainExitException(1, errorMessage);
mee.setUsageError(true);
throw mee;
}
private String grabOptionalValue() {
characterIndex++;
if (characterIndex < arguments[argumentIndex].length()) {
return arguments[argumentIndex].substring(characterIndex);
}
return null;
}
}
public byte[] inlineScript() {
return inlineScript.toString().getBytes();
}
public List<String> requiredLibraries() {
return requiredLibraries;
}
public List<String> loadPaths() {
return loadPaths;
}
public boolean shouldRunInterpreter() {
if(isShowVersion() && (hasInlineScript || scriptFileName != null)) {
return true;
}
return isShouldRunInterpreter();
}
public boolean shouldPrintUsage() {
return shouldPrintUsage;
}
public boolean shouldPrintProperties() {
return shouldPrintProperties;
}
private boolean isSourceFromStdin() {
return getScriptFileName() == null;
}
public boolean isInlineScript() {
return hasInlineScript;
}
public InputStream getScriptSource() {
try {
// KCode.NONE is used because KCODE does not affect parse in Ruby 1.8
// if Ruby 2.0 encoding pragmas are implemented, this will need to change
if (hasInlineScript) {
return new ByteArrayInputStream(inlineScript());
} else if (isSourceFromStdin()) {
// can't use -v and stdin
if (isShowVersion()) {
return null;
}
return getInput();
} else {
File file = JRubyFile.create(getCurrentDirectory(), getScriptFileName());
return new BufferedInputStream(new FileInputStream(file));
}
} catch (IOException e) {
throw new MainExitException(1, "Error opening script file: " + e.getMessage());
}
}
public String displayedFileName() {
if (hasInlineScript) {
if (scriptFileName != null) {
return scriptFileName;
} else {
return "-e";
}
} else if (isSourceFromStdin()) {
return "-";
} else {
return getScriptFileName();
}
}
private void setScriptFileName(String scriptFileName) {
this.scriptFileName = scriptFileName;
}
public String getScriptFileName() {
return scriptFileName;
}
public boolean isBenchmarking() {
return benchmarking;
}
public boolean isAssumeLoop() {
return assumeLoop;
}
public boolean isAssumePrinting() {
return assumePrinting;
}
public boolean isProcessLineEnds() {
return processLineEnds;
}
public boolean isSplit() {
return split;
}
public boolean isVerbose() {
return verbose == Boolean.TRUE;
}
public Boolean getVerbose() {
return verbose;
}
public boolean isDebug() {
return debug;
}
public boolean isShowVersion() {
return showVersion;
}
public boolean isShowBytecode() {
return showBytecode;
}
public boolean isShowCopyright() {
return showCopyright;
}
protected void setShowVersion(boolean showVersion) {
this.showVersion = showVersion;
}
protected void setShowBytecode(boolean showBytecode) {
this.showBytecode = showBytecode;
}
protected void setShowCopyright(boolean showCopyright) {
this.showCopyright = showCopyright;
}
public boolean isShouldRunInterpreter() {
return shouldRunInterpreter;
}
public boolean isShouldCheckSyntax() {
return shouldCheckSyntax;
}
public boolean isYARVEnabled() {
return yarv;
}
public String getInputFieldSeparator() {
return inputFieldSeparator;
}
public boolean isRubiniusEnabled() {
return rubinius;
}
public boolean isYARVCompileEnabled() {
return yarvCompile;
}
public KCode getKCode() {
return kcode;
}
public String getRecordSeparator() {
return recordSeparator;
}
public int getSafeLevel() {
return safeLevel;
}
public void setRecordSeparator(String recordSeparator) {
this.recordSeparator = recordSeparator;
}
public ClassCache getClassCache() {
return classCache;
}
public void setClassCache(ClassCache classCache) {
this.classCache = classCache;
}
public Map getOptionGlobals() {
return optionGlobals;
}
public boolean isManagementEnabled() {
return managementEnabled;
}
public Set getExcludedMethods() {
return excludedMethods;
}
}