001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.ruby.platform;
042:
043: import java.io.File;
044: import java.io.IOException;
045: import java.nio.charset.Charset;
046: import java.util.ArrayList;
047: import java.util.LinkedList;
048: import java.util.List;
049: import org.netbeans.api.queries.FileEncodingQuery;
050: import org.netbeans.api.ruby.platform.RubyPlatform;
051: import org.netbeans.modules.ruby.platform.execution.ExecutionDescriptor;
052: import org.netbeans.modules.ruby.platform.execution.ExecutionService;
053: import org.netbeans.modules.ruby.platform.execution.RegexpOutputRecognizer;
054: import org.openide.filesystems.FileObject;
055: import org.openide.modules.InstalledFileLocator;
056: import org.openide.util.Exceptions;
057: import org.openide.util.Utilities;
058:
059: /**
060: * Execution service for Ruby. Performs some Ruby specific setup like
061: * setting environment required for JRuby, or enabling I/O syncing for
062: * native Ruby.
063: *
064: * @todo Set HTTP_PROXY in the launched process, as is done for GemManager?
065: * See issue 111680 for details - http://www.netbeans.org/issues/show_bug.cgi?id=111680
066: *
067: * @author Tor Norbye
068: */
069: public class RubyExecution extends ExecutionService {
070:
071: private static final String WINDOWS_DRIVE = "(?:\\S{1}:[\\\\/])"; // NOI18N
072: private static final String FILE_CHAR = "[^\\s\\[\\]\\:\\\"]"; // NOI18N
073: private static final String FILE = "((?:" + FILE_CHAR + "*))"; // NOI18N
074: private static final String FILE_WIN = "(" + WINDOWS_DRIVE + "(?:"
075: + FILE_CHAR + ".*))"; // NOI18N
076: private static final String LINE = "([1-9][0-9]*)"; // NOI18N
077: private static final String ROL = ".*\\s?"; // NOI18N
078: private static final String SEP = "\\:"; // NOI18N
079: private static final String STD_SUFFIX = FILE + SEP + LINE + ROL;
080:
081: private static List<RegexpOutputRecognizer> stdRubyRecognizers;
082:
083: private static final RegexpOutputRecognizer RUBY_COMPILER = new RegexpOutputRecognizer(
084: ".*?" + STD_SUFFIX); // NOI18N
085:
086: private static final RegexpOutputRecognizer RUBY_COMPILER_WIN_MY = new RegexpOutputRecognizer(
087: ".*?" + FILE_WIN + SEP + LINE + ROL); // NOI18N
088:
089: /* Keeping old one. Get rid of this with more specific recongizers? */
090: private static final RegexpOutputRecognizer RUBY_COMPILER_WIN = new RegexpOutputRecognizer(
091: "^(?:(?:\\[|\\]|\\-|\\:|[0-9]|\\s|\\,)*)(?:\\s*from )?"
092: + FILE_WIN + SEP + LINE + ROL); // NOI18N
093:
094: private static final RegexpOutputRecognizer RAILS_RECOGNIZER = new RegexpOutputRecognizer(
095: ".*#\\{RAILS_ROOT\\}/" + STD_SUFFIX); // NOI18N
096:
097: public static final RegexpOutputRecognizer RUBY_TEST_OUTPUT = new RegexpOutputRecognizer(
098: "\\s*test.*\\[" + STD_SUFFIX); // NOI18N
099:
100: // TODO - add some more recognizers here which recognize the prefix path to Ruby (gems, GEM_HOME, etc.) such that I
101: // can hyperlink to errors in the "rake", "rails" etc. load scripts
102:
103: /** When not set (the default) do stdio syncing for native Ruby binaries */
104: private static final boolean SYNC_RUBY_STDIO = System
105: .getProperty("ruby.no.sync-stdio") == null; // NOI18N
106:
107: /** Set to suppress using the -Kkcode flag in case you're using a weird interpreter which doesn't support it */
108: //private static final boolean SKIP_KCODE = System.getProperty("ruby.no.kcode") == null; // NOI18N
109: private static final boolean SKIP_KCODE = true;
110:
111: /** When not set (the default) bypass the JRuby launcher unix/ba-file scripts and launch VM directly */
112: public static final boolean LAUNCH_JRUBY_SCRIPT = System
113: .getProperty("ruby.use.jruby.script") != null; // NOI18N
114:
115: private String charsetName;
116:
117: public RubyExecution(ExecutionDescriptor descriptor) {
118: super (descriptor);
119:
120: assert descriptor != null : "null descriptor";
121:
122: if (descriptor.getCmd() == null) {
123: descriptor.cmd(descriptor.getPlatform()
124: .getInterpreterFile());
125: }
126:
127: descriptor.addBinPath(true);
128: }
129:
130: /** Create a Ruby execution service with the given source-encoding charset */
131: public RubyExecution(ExecutionDescriptor descriptor,
132: String charsetName) {
133: this (descriptor);
134: this .charsetName = charsetName;
135: }
136:
137: public synchronized static List<? extends RegexpOutputRecognizer> getStandardRubyRecognizers() {
138: if (stdRubyRecognizers == null) {
139: stdRubyRecognizers = new LinkedList<RegexpOutputRecognizer>();
140: stdRubyRecognizers.add(RubyExecution.RAILS_RECOGNIZER);
141: stdRubyRecognizers.add(RubyExecution.RUBY_COMPILER_WIN_MY);
142: stdRubyRecognizers.add(RubyExecution.RUBY_COMPILER);
143: stdRubyRecognizers.add(RubyExecution.RUBY_COMPILER_WIN);
144: }
145: return stdRubyRecognizers;
146: }
147:
148: /**
149: * Returns the basic Ruby interpreter command and associated flags (not
150: * application arguments)
151: */
152: public static List<? extends String> getRubyArgs(
153: final RubyPlatform platform) {
154: return new RubyExecution(new ExecutionDescriptor(platform))
155: .getRubyArgs(platform.getHome().getAbsolutePath(),
156: platform.getInterpreterFile().getName(), null);
157: }
158:
159: private List<? extends String> getRubyArgs(String rubyHome,
160: String cmdName, ExecutionDescriptor descriptor) {
161: List<String> argvList = new ArrayList<String>();
162: // Decide whether I'm launching JRuby, and if so, take a shortcut and launch
163: // the VM directly. This is important because killing JRuby via the launcher script
164: // is not working right; now that JRuby on Unix exec's the VM that part is okay but
165: // on Windows there are still problems.
166: if (!LAUNCH_JRUBY_SCRIPT && cmdName.startsWith("jruby")) { // NOI18N
167: String javaHome = getJavaHome();
168:
169: argvList.add(javaHome + File.separator + "bin"
170: + File.separator + // NOI18N
171: "java"); // NOI18N
172: // XXX Do I need java.exe on Windows?
173:
174: // Additional execution flags specified in the JRuby startup script:
175: argvList.add("-Xverify:none"); // NOI18N
176: argvList.add("-da"); // NOI18N
177:
178: String extraArgs = System.getenv("JRUBY_EXTRA_VM_ARGS"); // NOI18N
179:
180: String javaMemory = "-Xmx512m"; // NOI18N
181: String javaStack = "-Xss1024k"; // NOI18N
182:
183: if (extraArgs != null) {
184: if (extraArgs.indexOf("-Xmx") != -1) { // NOI18N
185: javaMemory = null;
186: }
187: if (extraArgs.indexOf("-Xss") != -1) { // NOI18N
188: javaStack = null;
189: }
190: String[] jrubyArgs = Utilities
191: .parseParameters(extraArgs);
192: for (String arg : jrubyArgs) {
193: argvList.add(arg);
194: }
195: }
196:
197: if (javaMemory != null) {
198: argvList.add(javaMemory);
199: }
200: if (javaStack != null) {
201: argvList.add(javaStack);
202: }
203:
204: // Classpath
205: argvList.add("-classpath"); // NOI18N
206:
207: File rubyHomeDir = null;
208:
209: try {
210: rubyHomeDir = new File(rubyHome);
211: rubyHomeDir = rubyHomeDir.getCanonicalFile();
212: } catch (IOException ioe) {
213: Exceptions.printStackTrace(ioe);
214: }
215:
216: if (!rubyHomeDir.isDirectory()) {
217: throw new IllegalArgumentException(rubyHomeDir
218: .getAbsolutePath()
219: + " does not exist."); // NOI18N
220: }
221:
222: File jrubyLib = new File(rubyHomeDir, "lib"); // NOI18N
223: if (!jrubyLib.isDirectory()) {
224: throw new AssertionError('"'
225: + jrubyLib.getAbsolutePath() + "\" exists (\""
226: + descriptor.getCmd()
227: + "\" is not valid JRuby executable?)");
228: }
229:
230: argvList.add(computeJRubyClassPath(
231: descriptor == null ? null : descriptor
232: .getClassPath(), jrubyLib));
233:
234: argvList.add("-Djruby.base=" + rubyHomeDir); // NOI18N
235: argvList.add("-Djruby.home=" + rubyHomeDir); // NOI18N
236: argvList.add("-Djruby.lib=" + jrubyLib); // NOI18N
237:
238: // TODO - turn off verifier?
239:
240: if (Utilities.isWindows()) {
241: argvList.add("-Djruby.shell=\"cmd.exe\""); // NOI18N
242: argvList.add("-Djruby.script=jruby.bat"); // NOI18N
243: } else {
244: argvList.add("-Djruby.shell=/bin/sh"); // NOI18N
245: argvList.add("-Djruby.script=jruby"); // NOI18N
246: }
247:
248: // Main class
249: argvList.add("org.jruby.Main"); // NOI18N
250:
251: // TODO: JRUBYOPTS
252:
253: // Application arguments follow
254: }
255:
256: if (!SKIP_KCODE && cmdName.startsWith("ruby")) { // NOI18N
257: String cs = charsetName;
258: if (cs == null) {
259: // Add project encoding flags
260: FileObject fo = descriptor.getFileObject();
261: if (fo != null) {
262: Charset charset = FileEncodingQuery.getEncoding(fo);
263: if (charset != null) {
264: cs = charset.name();
265: }
266: }
267: }
268:
269: if (cs != null) {
270: if (cs.equals("UTF-8")) { // NOI18N
271: argvList.add("-Ku"); // NOI18N
272: //} else if (cs.equals("")) {
273: // What else???
274: }
275: }
276: }
277:
278: // Is this a native Ruby process? If so, do sync-io workaround.
279: if (SYNC_RUBY_STDIO && cmdName.startsWith("ruby")) { // NOI18N
280:
281: int dot = cmdName.indexOf('.');
282:
283: if ((dot == -1) || (dot == 4) || (dot == 5)) { // 5: rubyw
284:
285: InstalledFileLocator locator = InstalledFileLocator
286: .getDefault();
287: File f = locator
288: .locate(
289: "modules/org-netbeans-modules-ruby-project.jar", // NOI18N
290: null, false); // NOI18N
291:
292: if (f == null) {
293: throw new RuntimeException("Can't find cluster"); // NOI18N
294: }
295:
296: f = new File(f.getParentFile().getParentFile()
297: .getAbsolutePath()
298: + File.separator + "sync-stdio.rb"); // NOI18N
299:
300: try {
301: f = f.getCanonicalFile();
302: } catch (IOException ioe) {
303: Exceptions.printStackTrace(ioe);
304: }
305:
306: argvList.add("-r" + f.getAbsolutePath()); // NOI18N
307: }
308: }
309: return argvList;
310: }
311:
312: @Override
313: protected List<? extends String> buildArgs() {
314: List<String> argvList = new ArrayList<String>();
315: String rubyHome = descriptor.getCmd().getParentFile()
316: .getParent();
317: String cmdName = descriptor.getCmd().getName();
318: argvList.addAll(getRubyArgs(rubyHome, cmdName, descriptor));
319: argvList.addAll(super .buildArgs());
320: return argvList;
321: }
322:
323: public static String getJavaHome() {
324: String javaHome = System.getProperty("jruby.java.home"); // NOI18N
325:
326: if (javaHome == null) {
327: javaHome = System.getProperty("java.home"); // NOI18N
328: }
329:
330: return javaHome;
331: }
332:
333: /** Package-private for unit test. */
334: static String computeJRubyClassPath(String extraCp,
335: final File jrubyLib) {
336: StringBuilder cp = new StringBuilder();
337: File[] libs = jrubyLib.listFiles();
338:
339: for (File lib : libs) {
340: if (lib.getName().endsWith(".jar")) { // NOI18N
341:
342: if (cp.length() > 0) {
343: cp.append(File.pathSeparatorChar);
344: }
345:
346: cp.append(lib.getAbsolutePath());
347: }
348: }
349:
350: // Add in user-specified jars passed via JRUBY_EXTRA_CLASSPATH
351:
352: if (extraCp != null && File.pathSeparatorChar != ':') {
353: // Ugly hack - getClassPath has mixed together path separator chars
354: // (:) and filesystem separators, e.g. I might have C:\foo:D:\bar but
355: // obviously only the path separator after "foo" should be changed to ;
356: StringBuilder p = new StringBuilder();
357: int pathOffset = 0;
358: for (int i = 0; i < extraCp.length(); i++) {
359: char c = extraCp.charAt(i);
360: if (c == ':' && pathOffset != 1) {
361: p.append(File.pathSeparatorChar);
362: pathOffset = 0;
363: continue;
364: } else {
365: pathOffset++;
366: }
367: p.append(c);
368: }
369: extraCp = p.toString();
370: }
371:
372: if (extraCp == null) {
373: extraCp = System.getenv("JRUBY_EXTRA_CLASSPATH"); // NOI18N
374: }
375:
376: if (extraCp != null) {
377: if (cp.length() > 0) {
378: cp.append(File.pathSeparatorChar);
379: }
380: //if (File.pathSeparatorChar != ':' && extraCp.indexOf(File.pathSeparatorChar) == -1 &&
381: // extraCp.indexOf(':') != -1) {
382: // extraCp = extraCp.replace(':', File.pathSeparatorChar);
383: //}
384: cp.append(extraCp);
385: }
386: return Utilities.isWindows() ? "\"" + cp.toString() + "\"" : cp
387: .toString(); // NOI18N
388: }
389: }
|