Use a single evaluation enviroment for rules. (#84)

The rule extractor interprets Skylark code as Python, in an
environment with most Skylark globals and primitives stubbed out. It
was doing so in two environments, one for globals and one for locals.
But the Python documentation has this to say:

> If two separate objects are passed as globals and locals, the code
> will be executed as if it were embedded in a class definition.

This means that using two environments can have suprising results. One
of which is that calling recursive functions don't work, whereas it
works in Skylark or at the Python top-level. This also means that
calling a function `foo()` that itself depends on a function `bar()`
in the same Skylark file doesn't work.

So we instead `exec ...` in a single environment. We infer new
definitions contained in the input file by diffing the hold state and
the new state of the environment.
diff --git a/skydoc/rule_extractor.py b/skydoc/rule_extractor.py
index 6bc9360..af0ff16 100644
--- a/skydoc/rule_extractor.py
+++ b/skydoc/rule_extractor.py
@@ -96,11 +96,14 @@
     compiled = None
     with open(bzl_file) as f:
       compiled = compile(f.read(), bzl_file, 'exec')
-    skylark_locals = {}
     global_stubs = create_stubs(SKYLARK_STUBS, load_symbols)
-    exec(compiled) in global_stubs, skylark_locals
+    env = global_stubs.copy()
+    exec(compiled) in env
 
-    for name, obj in skylark_locals.iteritems():
+    new_globals = (
+      defn for defn in env.iteritems() if not global_stubs.has_key(defn[0])
+    )
+    for name, obj in new_globals:
       if (isinstance(obj, skylark_globals.RuleDescriptor) and
           not name.startswith('_')):
         obj.attrs['name'] = attr.AttrDescriptor(
diff --git a/skydoc/rule_extractor_test.py b/skydoc/rule_extractor_test.py
index d5e0c27..02386fe 100644
--- a/skydoc/rule_extractor_test.py
+++ b/skydoc/rule_extractor_test.py
@@ -311,6 +311,35 @@
 
     self.check_protos(src, expected)
 
+  def test_rule_with_generated_impl(self):
+    src = textwrap.dedent("""\
+        def real_impl(ctx):
+          return struct()
+
+        def macro():
+          return real_impl
+
+        impl = macro()
+
+        thisrule = rule(
+            implementation = impl,
+        )
+        """)
+
+    expected = textwrap.dedent("""\
+        rule {
+          name: "thisrule"
+          attribute {
+            name: "name"
+            type: UNKNOWN
+            mandatory: true
+          }
+          type: RULE
+        }
+        """)
+
+    self.check_protos(src, expected)
+
   def test_multi_line_description(self):
     src = textwrap.dedent("""\
         def _impl(ctx):