Patchwork [4,of,6] import-checker: guess what module is imported by list of locally defined ones

login
register
mail settings
Submitter Katsunori FUJIWARA
Date May 13, 2015, 4:53 p.m.
Message ID <9b0ee3a4e1c6ca73bd12.1431536037@juju>
Download mbox | patch
Permalink /patch/9047/
State Accepted
Headers show

Comments

Katsunori FUJIWARA - May 13, 2015, 4:53 p.m.
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1431535750 -32400
#      Thu May 14 01:49:10 2015 +0900
# Node ID 9b0ee3a4e1c6ca73bd12c0d7ab16b066c807428c
# Parent  be2b8d8ae72918035dbb1ab15bf6c6b3919dd40e
import-checker: guess what module is imported by list of locally defined ones

Before this patch, to guess what module is imported in each modules,
"checkmod()" examines whether imported module is one of standard
Python library or not.

But this causes some problems below:

  - cycle via some modules may be overlooked

    name of some mercurial specific modules collides against one of
    standard library (e.g. commands, parser and formatter), and this
    aborts detection for some cycles.

  - it is difficult to list up enough module names of standard
    library in the portable and robust way

    for example, see fbdbff1b486a of Windows environment

To avoid problems above, this patch guesses what module is imported by
list of locally defined (= mercurial specific) ones.

This logic is reasonable, because locally defined module should be
imported via "import xxxx" (without "from yyyy"), even if its name
collides against one of standard library.

It is assumed that all locally defined modules are correctly specified
to "import-checker.py" at once.

Patch

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -217,14 +217,16 @@  def verify_stdlib_on_own_line(source, mo
 class CircularImport(Exception):
     pass
 
-def checkmod(mod, imports):
+def checkmod(mod, imports, localmods):
+    prefix = getprefix(mod)
+    fromlocal = fromlocalfunc(mod, localmods)
     shortest = {}
     visit = [[mod]]
     while visit:
         path = visit.pop(0)
         for i in sorted(imports.get(path[-1], [])):
-            if i not in stdlib_modules and not i.startswith('mercurial.'):
-                i = mod.rsplit('.', 1)[0] + '.' + i
+            if fromlocal(i):
+                i = prefix + i
             if len(path) < shortest.get(i, 1000):
                 shortest[i] = len(path)
                 if i in path:
@@ -243,21 +245,25 @@  def rotatecycle(cycle):
     idx = cycle.index(lowest)
     return cycle[idx:] + cycle[:idx] + [lowest]
 
-def find_cycles(imports):
+def find_cycles(imports, localmods):
     """Find cycles in an already-loaded import graph.
 
+    >>> localmods = {'top.foo': True,
+    ...              'top.bar': True,
+    ...              'top.baz': True,
+    ...              'top.qux': True}
     >>> imports = {'top.foo': ['bar', 'os.path', 'qux'],
     ...            'top.bar': ['baz', 'sys'],
     ...            'top.baz': ['foo'],
     ...            'top.qux': ['foo']}
-    >>> print '\\n'.join(sorted(find_cycles(imports)))
+    >>> print '\\n'.join(sorted(find_cycles(imports, localmods)))
     top.bar -> top.baz -> top.foo -> top.bar
     top.foo -> top.qux -> top.foo
     """
     cycles = set()
     for mod in sorted(imports.iterkeys()):
         try:
-            checkmod(mod, imports)
+            checkmod(mod, imports, localmods)
         except CircularImport, e:
             cycle = e.args[0]
             cycles.add(" -> ".join(rotatecycle(cycle)))
@@ -288,7 +294,7 @@  def main(argv):
             any_errors = True
             print source_path, error
         f.close()
-    cycles = find_cycles(used_imports)
+    cycles = find_cycles(used_imports, localmods)
     if cycles:
         firstmods = set()
         for c in sorted(cycles, key=_cycle_sortkey):
diff --git a/tests/test-module-imports.t b/tests/test-module-imports.t
--- a/tests/test-module-imports.t
+++ b/tests/test-module-imports.t
@@ -21,3 +21,4 @@  these may expose other cycles.
 
   $ hg locate 'mercurial/**.py' | sed 's-\\-/-g' | python "$import_checker" -
   Import cycle: mercurial.cmdutil -> mercurial.context -> mercurial.subrepo -> mercurial.cmdutil
+  Import cycle: mercurial.commands -> mercurial.commandserver -> mercurial.dispatch -> mercurial.commands