OSDN Git Service

Bridge MonkeyRunnerExported tags into pydoc.
authorBill Napier <napier@google.com>
Sat, 2 Oct 2010 03:58:40 +0000 (20:58 -0700)
committerBill Napier <napier@google.com>
Thu, 7 Oct 2010 18:04:16 +0000 (11:04 -0700)
This allows the use of pydoc to generate MonkeyRunner
API documentation.

Change-Id: I0af981f2023abf2cbf92cb1d7c9132936414c559

build/tools.atree
monkeyrunner/scripts/mr_pydoc.py [new file with mode: 0644]
monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java
monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java
monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
monkeyrunner/src/com/android/monkeyrunner/doc/MonkeyRunnerExported.java

index 55ad757..4a3f2e9 100644 (file)
@@ -46,6 +46,7 @@ bin/draw9patch      tools/draw9patch
 bin/layoutopt       tools/layoutopt
 bin/traceview       tools/traceview
 bin/android         tools/android
+bin/monkeyrunner    tools/monkeyrunner
 
 
 # sdk.git Ant templates for project build files
@@ -83,6 +84,9 @@ framework/anttasks.jar           tools/lib/anttasks.jar
 framework/sdklib.jar             tools/lib/sdklib.jar
 framework/sdkuilib.jar           tools/lib/sdkuilib.jar
 framework/sdkmanager.jar         tools/lib/sdkmanager.jar
+framework/monkeyrunner.jar       tools/lib/monkeyrunner.jar
+framework/gauvalib.jar           tools/lib/guavalib.jar
+framework/jsilver.jar            tools/lib/jsilver.jar
 
 # 3rd Party java libraries
 framework/groovy-all-1.7.0.jar                                tools/lib/groovy-all-1.7.0.jar
@@ -109,4 +113,3 @@ external/proguard/bin/proguard.sh                             tools/proguard/bin
 external/proguard/bin/proguardgui.sh                          tools/proguard/bin/proguardgui.sh
 external/proguard/bin/retrace.sh                              tools/proguard/bin/retrace.sh
 external/proguard/src/proguard/ant/task.properties            tools/proguard/ant/task.properties
-
diff --git a/monkeyrunner/scripts/mr_pydoc.py b/monkeyrunner/scripts/mr_pydoc.py
new file mode 100644 (file)
index 0000000..5c35296
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/bin/env monkeyrunner
+# Copyright 2010, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import com.android.monkeyrunner.MonkeyRunnerHelp as mrh
+import pydoc
+import sys
+
+def create_page(title, document):
+  return """
+page.title=%s
+@jd:body
+%s
+</body>
+</html>
+""" % (title, document)
+
+BASEDIR = 'frameworks/base/docs/html/guide/topics/testing/'
+
+def main():
+  document = ""
+
+  for clz in mrh.getAllDocumentedClasses():
+    object, name = pydoc.resolve(str(clz), 0)
+    document += pydoc.html.document(object, name)
+
+  page = create_page('MonkeyRunner API', document)
+  file = open(BASEDIR + 'monkeyrunner_api.html', 'w')
+  file.write(page)
+  file.close()
+
+if __name__ == '__main__':
+  main()
index 258261b..8d25dd9 100644 (file)
  */
 package com.android.monkeyrunner;
 
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.ImmutableMap.Builder;
-
-import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.text.BreakIterator;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import org.python.core.ArgParser;
+import org.python.core.ClassDictInit;
 import org.python.core.Py;
 import org.python.core.PyDictionary;
 import org.python.core.PyFloat;
@@ -30,16 +38,21 @@ import org.python.core.PyInteger;
 import org.python.core.PyList;
 import org.python.core.PyNone;
 import org.python.core.PyObject;
+import org.python.core.PyReflectedFunction;
 import org.python.core.PyString;
+import org.python.core.PyStringMap;
 import org.python.core.PyTuple;
 
-import java.lang.reflect.Method;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableMap.Builder;
 
 /**
  * Collection of useful utilities function for interacting with the Jython interpreter.
@@ -233,4 +246,219 @@ public final class JythonUtils {
         }
         return new PyDictionary(resultMap);
     }
+
+    /**
+     * This function should be called from classDictInit for any classes that are being exported
+     * to jython.  This jython converts all the MonkeyRunnerExported annotations for the given class
+     * into the proper python form.  It also removes any functions listed in the dictionary that
+     * aren't specifically annotated in the java class.
+     *
+     * NOTE: Make sure the calling class implements {@link ClassDictInit} to ensure that
+     * classDictInit gets called.
+     *
+     * @param clz the class to examine.
+     * @param dict the dictionary to update.
+     */
+    public static void convertDocAnnotationsForClass(Class<?> clz, PyObject dict) {
+      Preconditions.checkNotNull(dict);
+      Preconditions.checkArgument(dict instanceof PyStringMap);
+
+      // See if the class has the annotation
+      if (clz.isAnnotationPresent(MonkeyRunnerExported.class)) {
+        MonkeyRunnerExported doc = clz.getAnnotation(MonkeyRunnerExported.class);
+        String fullDoc = buildClassDoc(doc, clz);
+        dict.__setitem__("__doc__", new PyString(fullDoc));
+      }
+
+      // Get all the keys from the dict and put them into a set.  As we visit the annotated methods,
+      // we will remove them from this set.  At the end, these are the "hidden" methods that
+      // should be removed from the dict
+      Collection<String> functions = Sets.newHashSet();
+      for (PyObject item : dict.asIterable()) {
+        functions.add(item.toString());
+      }
+
+      // And remove anything that starts with __, as those are pretty important to retain
+      functions = Collections2.filter(functions, new Predicate<String>() {
+        @Override
+        public boolean apply(String value) {
+          return !value.startsWith("__");
+        }
+      });
+
+      // Look at all the methods in the class and find the one's that have the
+      // @MonkeyRunnerExported annotation.
+      for (Method m : clz.getMethods()) {
+        if (m.isAnnotationPresent(MonkeyRunnerExported.class)) {
+          String methodName = m.getName();
+          PyObject pyFunc = dict.__finditem__(methodName);
+          if (pyFunc != null && pyFunc instanceof PyReflectedFunction) {
+            PyReflectedFunction realPyFunc = (PyReflectedFunction) pyFunc;
+            MonkeyRunnerExported doc = m.getAnnotation(MonkeyRunnerExported.class);
+
+            realPyFunc.__doc__ = new PyString(buildDoc(doc));
+            functions.remove(methodName);
+          }
+        }
+      }
+
+      // Now remove any elements left from the functions collection
+      for (String name : functions) {
+        dict.__delitem__(name);
+      }
+    }
+
+    private static final Predicate<AccessibleObject> SHOULD_BE_DOCUMENTED = new Predicate<AccessibleObject>() {
+         @Override
+         public boolean apply(AccessibleObject ao) {
+             return ao.isAnnotationPresent(MonkeyRunnerExported.class);
+         }
+    };
+    private static final Predicate<Field> IS_FIELD_STATIC = new Predicate<Field>() {
+        @Override
+        public boolean apply(Field f) {
+            return (f.getModifiers() & Modifier.STATIC) != 0;
+        }
+    };
+
+    /**
+     * build a jython doc-string for a class from the annotation and the fields
+     * contained within the class
+     *
+     * @param doc the annotation
+     * @param clz the class to be documented
+     * @return the doc-string
+     */
+    private static String buildClassDoc(MonkeyRunnerExported doc, Class<?> clz) {
+        // Below the class doc, we need to document all the documented field this class contains
+        Collection<Field> annotatedFields = Collections2.filter(Arrays.asList(clz.getFields()), SHOULD_BE_DOCUMENTED);
+        Collection<Field> staticFields = Collections2.filter(annotatedFields, IS_FIELD_STATIC);
+        Collection<Field> nonStaticFields = Collections2.filter(annotatedFields, Predicates.not(IS_FIELD_STATIC));
+
+        StringBuilder sb = new StringBuilder();
+        for (String line : splitString(doc.doc(), 80)) {
+            sb.append(line).append("\n");
+        }
+
+        if (staticFields.size() > 0) {
+            sb.append("\nClass Fields: \n");
+            for (Field f : staticFields) {
+                sb.append(buildFieldDoc(f));
+            }
+        }
+
+        if (nonStaticFields.size() > 0) {
+            sb.append("\n\nFields: \n");
+            for (Field f : nonStaticFields) {
+                sb.append(buildFieldDoc(f));
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Build a doc-string for the annotated field.
+     *
+     * @param f the field.
+     * @return the doc-string.
+     */
+    private static String buildFieldDoc(Field f) {
+       MonkeyRunnerExported annotation = f.getAnnotation(MonkeyRunnerExported.class);
+       StringBuilder sb = new StringBuilder();
+       int indentOffset = 2 + 3 + f.getName().length();
+       String indent = makeIndent(indentOffset);
+
+       sb.append("  ").append(f.getName()).append(" - ");
+
+       boolean first = true;
+       for (String line : splitString(annotation.doc(), 80 - indentOffset)) {
+           if (first) {
+               first = false;
+               sb.append(line).append("\n");
+           } else {
+               sb.append(indent).append(line).append("\n");
+           }
+       }
+
+
+       return sb.toString();
+    }
+
+    /**
+     * Build a jython doc-string from the MonkeyRunnerExported annotation.
+     *
+     * @param doc the annotation to build from
+     * @return a jython doc-string
+     */
+    private static String buildDoc(MonkeyRunnerExported doc) {
+        Collection<String> docs = splitString(doc.doc(), 80);
+        StringBuilder sb = new StringBuilder();
+        for (String d : docs) {
+            sb.append(d).append("\n");
+        }
+
+        if (doc.args() != null && doc.args().length > 0) {
+            String[] args = doc.args();
+            String[] argDocs = doc.argDocs();
+
+            sb.append("\n  Args:\n");
+            for (int x = 0; x < doc.args().length; x++) {
+                sb.append("    ").append(args[x]);
+                if (argDocs != null && argDocs.length > x) {
+                    sb.append(" - ");
+                    int indentOffset = args[x].length() + 3 + 4;
+                    Collection<String> lines = splitString(argDocs[x], 80 - indentOffset);
+                    boolean first = true;
+                    String indent = makeIndent(indentOffset);
+                    for (String line : lines) {
+                        if (first) {
+                            first = false;
+                            sb.append(line).append("\n");
+                        } else {
+                            sb.append(indent).append(line).append("\n");
+                        }
+                    }
+                }
+            }
+        }
+
+        return sb.toString();
+    }
+
+    private static String makeIndent(int indentOffset) {
+        if (indentOffset == 0) {
+            return "";
+        }
+        StringBuffer sb = new StringBuffer();
+        while (indentOffset > 0) {
+            sb.append(' ');
+            indentOffset--;
+        }
+        return sb.toString();
+    }
+
+    private static Collection<String> splitString(String source, int offset) {
+        BreakIterator boundary = BreakIterator.getLineInstance();
+        boundary.setText(source);
+
+        List<String> lines = Lists.newArrayList();
+        StringBuilder currentLine = new StringBuilder();
+        int start = boundary.first();
+
+        for (int end = boundary.next();
+                end != BreakIterator.DONE;
+                start = end, end = boundary.next()) {
+            String b = source.substring(start, end);
+            if (currentLine.length() + b.length() < offset) {
+                currentLine.append(b);
+            } else {
+                // emit the old line
+                lines.add(currentLine.toString());
+                currentLine = new StringBuilder(b);
+            }
+        }
+        lines.add(currentLine.toString());
+        return lines;
+    }
 }
index d150c9b..2d120f5 100644 (file)
  */
 package com.android.monkeyrunner;
 
-import com.google.common.base.Functions;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Collections2;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
 
-import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+import javax.annotation.Nullable;
 
 import org.python.core.ArgParser;
+import org.python.core.ClassDictInit;
 import org.python.core.Py;
 import org.python.core.PyDictionary;
 import org.python.core.PyException;
 import org.python.core.PyObject;
 import org.python.core.PyTuple;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-import javax.annotation.Nullable;
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
 
 /*
  * Abstract base class that represents a single connected Android
@@ -40,7 +42,31 @@ import javax.annotation.Nullable;
  * that device.  Each backend will need to create a concrete
  * implementation of this class.
  */
-public abstract class MonkeyDevice {
+@MonkeyRunnerExported(doc = "Represents a device attached to the system.")
+public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
+    public static void classDictInit(PyObject dict) {
+        JythonUtils.convertDocAnnotationsForClass(MonkeyDevice.class, dict);
+    }
+
+    @MonkeyRunnerExported(doc = "Sends a DOWN event when used with touch() or press().")
+    public static final String DOWN = "down";
+    @MonkeyRunnerExported(doc = "Sends an UP event when used with touch() or press().")
+    public static final String UP = "up";
+    @MonkeyRunnerExported(doc = "Sends a DOWN event, immediately followed by an UP event when used with touch() or press()")
+    public static final String DOWN_AND_UP = "downAndUp";
+
+    // Visible to subclasses
+    protected enum TouchPressType {
+        DOWN, UP, DOWN_AND_UP,
+    }
+
+    private static final Map<String, TouchPressType> TOUCH_NAME_TO_ENUM =
+        ImmutableMap.of(MonkeyDevice.DOWN, TouchPressType.DOWN,
+                MonkeyDevice.UP, TouchPressType.UP,
+                MonkeyDevice.DOWN_AND_UP, TouchPressType.DOWN_AND_UP);
+
+    private static final Set<String> VALID_DOWN_UP_TYPES = TOUCH_NAME_TO_ENUM.keySet();
+
     /**
      * Create a MonkeyMananger for talking to this device.
      *
@@ -85,15 +111,6 @@ public abstract class MonkeyDevice {
         return getSystemProperty(ap.getString(0));
     }
 
-    @MonkeyRunnerExported(doc = "Enumerates the possible touch and key event types.  Use this " +
-            "with touch() or press() to specify the event type.",
-            argDocs = {"Sends a DOWN event",
-            "Sends an UP event",
-            "Sends a DOWN event, immediately followed by an UP event"})
-    public enum TouchPressType {
-        DOWN, UP, DOWN_AND_UP
-    }
-
     @MonkeyRunnerExported(doc = "Sends a touch event at the specified location",
             args = { "x", "y", "type" },
             argDocs = { "x coordinate in pixels",
@@ -107,15 +124,20 @@ public abstract class MonkeyDevice {
         int y = ap.getInt(1);
 
         // Default
-        MonkeyDevice.TouchPressType type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+        String type = MonkeyDevice.DOWN_AND_UP;
         try {
-            PyObject pyObject = ap.getPyObject(2);
-            type = (TouchPressType) pyObject.__tojava__(MonkeyDevice.TouchPressType.class);
+            String tmpType = ap.getString(2);
+            if (VALID_DOWN_UP_TYPES.contains(tmpType)) {
+                type = tmpType;
+            } else {
+                // not a valid type
+                type = MonkeyDevice.DOWN_AND_UP;
+            }
         } catch (PyException e) {
             // bad stuff was passed in, just use the already specified default value
-            type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+            type = MonkeyDevice.DOWN_AND_UP;
         }
-        touch(x, y, type);
+        touch(x, y, TOUCH_NAME_TO_ENUM.get(type));
     }
 
     @MonkeyRunnerExported(doc = "Simulates dragging (touch, hold, and move) on the device screen.",
@@ -165,15 +187,20 @@ public abstract class MonkeyDevice {
         String name = ap.getString(0);
 
         // Default
-        MonkeyDevice.TouchPressType type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+        String type = MonkeyDevice.DOWN_AND_UP;
         try {
-            PyObject pyObject = ap.getPyObject(1);
-            type = (TouchPressType) pyObject.__tojava__(MonkeyDevice.TouchPressType.class);
+            String tmpType = ap.getString(2);
+            if (VALID_DOWN_UP_TYPES.contains(tmpType)) {
+                type = tmpType;
+            } else {
+                // not a valid type
+                type = MonkeyDevice.DOWN_AND_UP;
+            }
         } catch (PyException e) {
             // bad stuff was passed in, just use the already specified default value
-            type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+            type = MonkeyDevice.DOWN_AND_UP;
         }
-        press(name, type);
+        press(name, TOUCH_NAME_TO_ENUM.get(type));
     }
 
     @MonkeyRunnerExported(doc = "Types the specified string on the keyboard. This is " +
index 6e32b3d..d506613 100644 (file)
@@ -20,6 +20,7 @@ import com.google.common.base.Preconditions;
 import com.android.monkeyrunner.doc.MonkeyRunnerExported;
 
 import org.python.core.ArgParser;
+import org.python.core.ClassDictInit;
 import org.python.core.PyInteger;
 import org.python.core.PyObject;
 import org.python.core.PyTuple;
@@ -39,7 +40,12 @@ import javax.imageio.stream.ImageOutputStream;
 /**
  * Jython object to encapsulate images that have been taken.
  */
-public abstract class MonkeyImage {
+@MonkeyRunnerExported(doc = "An image")
+public abstract class MonkeyImage extends PyObject implements ClassDictInit {
+    public static void classDictInit(PyObject dict) {
+        JythonUtils.convertDocAnnotationsForClass(MonkeyImage.class, dict);
+    }
+
     /**
      * Convert the MonkeyImage into a BufferedImage.
      *
index 63ab9eb..648843d 100644 (file)
@@ -22,6 +22,7 @@ import com.google.common.collect.Collections2;
 import com.android.monkeyrunner.doc.MonkeyRunnerExported;
 
 import org.python.core.ArgParser;
+import org.python.core.ClassDictInit;
 import org.python.core.PyException;
 import org.python.core.PyObject;
 
@@ -34,10 +35,15 @@ import javax.swing.JOptionPane;
 /**
  * This is the main interface class into the jython bindings.
  */
-public class MonkeyRunner {
+@MonkeyRunnerExported(doc = "Main entry point for MonkeyRunner")
+public class MonkeyRunner extends PyObject implements ClassDictInit {
     private static final Logger LOG = Logger.getLogger(MonkeyRunner.class.getCanonicalName());
     private static MonkeyRunnerBackend backend;
 
+    public static void classDictInit(PyObject dict) {
+        JythonUtils.convertDocAnnotationsForClass(MonkeyRunner.class, dict);
+    }
+
     /**
      * Set the backend MonkeyRunner is using.
      *
index 124e6cf..746d240 100644 (file)
@@ -19,6 +19,7 @@ import com.google.clearsilver.jsilver.JSilver;
 import com.google.clearsilver.jsilver.data.Data;
 import com.google.clearsilver.jsilver.resourceloader.ClassLoaderResourceLoader;
 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Lists;
@@ -32,6 +33,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
@@ -238,4 +240,35 @@ public final class MonkeyRunnerHelp {
 
         return hdf;
     }
+
+    public static Collection<String> getAllDocumentedClasses() {
+        Set<Field> fields = Sets.newTreeSet(MEMBER_SORTER);
+        Set<Method> methods = Sets.newTreeSet(MEMBER_SORTER);
+        Set<Constructor<?>> constructors = Sets.newTreeSet(MEMBER_SORTER);
+        Set<Class<?>> classes = Sets.newTreeSet(CLASS_SORTER);
+        getAllExportedClasses(fields, methods, constructors, classes);
+
+        // The classes object only captures classes that are specifically exporter, which isn't
+        // good enough.  So go through all the fields, methods, etc. and collect those classes as
+        // as well
+        Set<Class<?>> allClasses = Sets.newHashSet();
+        allClasses.addAll(classes);
+        for (Field f : fields) {
+            allClasses.add(f.getDeclaringClass());
+        }
+        for (Method m : methods) {
+            allClasses.add(m.getDeclaringClass());
+        }
+        for (Constructor<?> constructor : constructors) {
+            allClasses.add(constructor.getDeclaringClass());
+        }
+
+        // And transform that collection into a list of simple names.
+        return Collections2.transform(allClasses, new Function<Class<?>, String>() {
+            @Override
+            public String apply(Class<?> clz) {
+                return clz.getName();
+            }
+        });
+    }
 }
index 763362e..60f059d 100644 (file)
@@ -22,6 +22,15 @@ import com.google.common.collect.ImmutableMap;
 import com.android.monkeyrunner.adb.AdbBackend;
 import com.android.monkeyrunner.stub.StubBackend;
 
+import org.python.core.Py;
+import org.python.core.PyBuiltinMethod;
+import org.python.core.PyDataDescr;
+import org.python.core.PyNewWrapper;
+import org.python.core.PyObject;
+import org.python.core.PySystemState;
+import org.python.core.PyType;
+import org.python.expose.BaseTypeBuilder;
+import org.python.expose.TypeBuilder;
 import org.python.util.PythonInterpreter;
 
 import java.io.File;
@@ -81,7 +90,7 @@ public class MonkeyRunnerStarter {
     private int run() {
         // This system property gets set by the included starter script
         String monkeyRunnerPath = System.getProperty("com.android.monkeyrunner.bindir") +
-            File.separator + "monkeyrunner";
+                File.separator + "monkeyrunner";
 
         MonkeyRunner.setBackend(backend);
         Map<String, Predicate<PythonInterpreter>> plugins = handlePlugins();
index b55be87..3c96eeb 100644 (file)
@@ -17,12 +17,16 @@ package com.android.monkeyrunner;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
 import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Lists;
 
 import org.python.core.Py;
 import org.python.core.PyException;
+import org.python.core.PyJavaPackage;
+import org.python.core.PyList;
 import org.python.core.PyObject;
+import org.python.core.PyString;
+import org.python.core.PySystemState;
 import org.python.util.InteractiveConsole;
 import org.python.util.JLineConsole;
 import org.python.util.PythonInterpreter;
@@ -105,6 +109,8 @@ public class ScriptRunner {
 
         // Bind __name__ to __main__ so mains will run
         python.set("__name__", "__main__");
+        // Also find __file__
+        python.set("__file__", scriptfilename);
 
         try {
           python.execfile(scriptfilename);
@@ -170,6 +176,12 @@ public class ScriptRunner {
         props.setProperty("python.executable", executablePath);
 
         PythonInterpreter.initialize(System.getProperties(), props, argv);
+
+        String frameworkDir = System.getProperty("java.ext.dirs");
+        File monkeyRunnerJar = new File(frameworkDir, "monkeyrunner.jar");
+        if (monkeyRunnerJar.canRead()) {
+            PySystemState.packageManager.addJar(monkeyRunnerJar.getAbsolutePath(), false);
+        }
     }
 
     /**
index dd3eb0b..33da2dd 100644 (file)
@@ -27,7 +27,8 @@ import java.lang.annotation.Target;
  * these methods in the scripting environment.
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE })
+@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR,
+          ElementType.TYPE, ElementType.FIELD })
 public @interface MonkeyRunnerExported {
     /**
      * A documentation string for this method.