[GRASS-SVN] r57910 - in grass/trunk: gui/wxpython/core lib/python/script

svn_grass at osgeo.org svn_grass at osgeo.org
Tue Oct 1 19:55:55 PDT 2013


Author: wenzeslaus
Date: 2013-10-01 19:55:55 -0700 (Tue, 01 Oct 2013)
New Revision: 57910

Modified:
   grass/trunk/gui/wxpython/core/gcmd.py
   grass/trunk/lib/python/script/core.py
   grass/trunk/lib/python/script/task.py
Log:
handling of scripts on MS Win using Python which (uses PATH) and sys.executable (for grass.script and wxGUI), fixes MAXREPEAT error when running Python script

Modified: grass/trunk/gui/wxpython/core/gcmd.py
===================================================================
--- grass/trunk/gui/wxpython/core/gcmd.py	2013-10-01 18:32:00 UTC (rev 57909)
+++ grass/trunk/gui/wxpython/core/gcmd.py	2013-10-02 02:55:55 UTC (rev 57910)
@@ -533,16 +533,21 @@
         self.startTime = time.time()
 
         # TODO: replace ugly hack below
+        # this cannot be replaced it can be only improved
+        # also unifying this with 3 other places in code would be nice
+        # changing from one chdir to get_real_command function
         args = self.cmd
         if sys.platform == 'win32':
-            ext = os.path.splitext(self.cmd[0])[1] == globalvar.SCT_EXT
-            if ext or self.cmd[0] in globalvar.grassScripts[globalvar.SCT_EXT]:
-                os.chdir(os.path.join(os.getenv('GISBASE'), 'scripts'))
-                if not ext:
-                    args = [sys.executable, self.cmd[0] + globalvar.SCT_EXT] + self.cmd[1:]
-                else:
-                    args = [sys.executable, self.cmd[0]] + self.cmd[1:]
-        
+            if os.path.splitext(args[0])[1] == globalvar.SCT_EXT:
+                args[0] = args[0][:-3]
+            # using Python executable to run the module if it is a script
+            # expecting at least module name at first position
+            # cannot use make_command for this now because it is used in GUI
+            # The same code is in grass.script.core already twice.
+            args[0] = grass.get_real_command(args[0])
+            if args[0].endswith('.py'):
+                args.insert(0, sys.executable)
+ 
         try:
             self.module = Popen(args,
                                 stdin = subprocess.PIPE,

Modified: grass/trunk/lib/python/script/core.py
===================================================================
--- grass/trunk/lib/python/script/core.py	2013-10-01 18:32:00 UTC (rev 57909)
+++ grass/trunk/lib/python/script/core.py	2013-10-02 02:55:55 UTC (rev 57910)
@@ -144,6 +144,112 @@
     return set(cmd), scripts
 
 
+# replacement for which function from shutil (not available in all versions)
+# from http://hg.python.org/cpython/file/6860263c05b3/Lib/shutil.py#l1068
+# added because of Python scripts running Python scripts on MS Windows
+# see also ticket #2008 which is unrelated but same function was proposed
+def shutil_which(cmd, mode=os.F_OK | os.X_OK, path=None):
+    """Given a command, mode, and a PATH string, return the path which
+    conforms to the given mode on the PATH, or None if there is no such
+    file.
+
+    `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
+    of os.environ.get("PATH"), or can be overridden with a custom search
+    path.
+
+    """
+    # Check that a given file can be accessed with the correct mode.
+    # Additionally check that `file` is not a directory, as on Windows
+    # directories pass the os.access check.
+    def _access_check(fn, mode):
+        return (os.path.exists(fn) and os.access(fn, mode)
+                and not os.path.isdir(fn))
+
+    # If we're given a path with a directory part, look it up directly rather
+    # than referring to PATH directories. This includes checking relative to the
+    # current directory, e.g. ./script
+    if os.path.dirname(cmd):
+        if _access_check(cmd, mode):
+            return cmd
+        return None
+
+    if path is None:
+        path = os.environ.get("PATH", os.defpath)
+    if not path:
+        return None
+    path = path.split(os.pathsep)
+
+    if sys.platform == "win32":
+        # The current directory takes precedence on Windows.
+        if not os.curdir in path:
+            path.insert(0, os.curdir)
+
+        # PATHEXT is necessary to check on Windows.
+        pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
+        # See if the given file matches any of the expected path extensions.
+        # This will allow us to short circuit when given "python.exe".
+        # If it does match, only test that one, otherwise we have to try
+        # others.
+        if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
+            files = [cmd]
+        else:
+            files = [cmd + ext for ext in pathext]
+    else:
+        # On other platforms you don't have things like PATHEXT to tell you
+        # what file suffixes are executable, so just pass on cmd as-is.
+        files = [cmd]
+
+    seen = set()
+    for dir in path:
+        normdir = os.path.normcase(dir)
+        if not normdir in seen:
+            seen.add(normdir)
+            for thefile in files:
+                name = os.path.join(dir, thefile)
+                if _access_check(name, mode):
+                    return name
+    return None
+
+
+# Added because of scripts calling scripts on MS Windows.
+# Module name (here cmd) differs from the file name (does not have extension).
+# Additionally, we don't run scripts using system executable mechanism,
+# so we need the full path name.
+# However, scripts are on the PATH and '.PY' in in PATHEXT, so we can use
+# shutil.which to get the full file path. Addons are on PATH too.
+# An alternative to which function call would be to check the script path and
+# addons path. This is proposed improvement for the future.
+# Another alternative is to check some global list of scripts but this list
+# needs to be created first. The question is what is less expensive.
+# Note that getting the full path is only part of the solution,
+# the other part is to use the right Python as an executable and pass the full
+# script path as a parameter.
+# Nevertheless, it is unclear on which places which extensions are added.
+# This function also could skip the check for platform but depends
+# how will be used, this is most general but not most effective.
+def get_real_command(cmd):
+    """!Returns the real file commad for a module (cmd)
+
+    For Python scripts on MS Windows it returns full path to the script
+    and adds a '.py' extension.
+    For other cases it just returns a module (name).
+    So, you can just use this function for all without further check.
+
+    >>> get_real_command('g.region')
+    'g.region'
+    """
+    if sys.platform == 'win32':
+        # we in fact expect pure module name (without extension)
+        # so, lets remove extension
+        if os.path.splitext(cmd)[1] == '.py':
+            cmd = cmd[:-3]
+        full_path = shutil_which(cmd + '.py')
+        if full_path:
+            return full_path
+
+    return cmd
+
+
 def make_command(prog, flags="", overwrite=False, quiet=False, verbose=False,
                  **options):
     """!Return a list of strings suitable for use as the args parameter to
@@ -221,6 +327,13 @@
             options[opt] = val
 
     args = make_command(prog, flags, overwrite, quiet, verbose, **options)
+    # using Python executable to run the module if it is a script
+    # expecting at least module name at first position
+    # cannot use make_command for this now because it is used in GUI
+    if sys.platform == "win32":
+        args[0] = get_real_command(args[0])
+        if args[0].endswith('.py'):
+            args.insert(0, sys.executable)
 
     if debug_level() > 0:
         sys.stderr.write("D1/%d: %s.start_command(): %s\n" % (debug_level(),
@@ -370,6 +483,14 @@
 
     """
     args = make_command(prog, flags, overwrite, quiet, verbose, **kwargs)
+    # using Python executable to run the module if it is a script
+    # expecting at least module name at first position
+    # cannot use make_command for this now because it is used in GUI
+    if sys.platform == "win32":
+        args[0] = get_real_command(args[0])
+        if args[0].endswith('.py'):
+            args.insert(0, sys.executable)
+
     if env == None:
         env = os.environ
     os.execvpe(prog, args, env)

Modified: grass/trunk/lib/python/script/task.py
===================================================================
--- grass/trunk/lib/python/script/task.py	2013-10-01 18:32:00 UTC (rev 57909)
+++ grass/trunk/lib/python/script/task.py	2013-10-02 02:55:55 UTC (rev 57910)
@@ -466,14 +466,17 @@
         cmdout, cmderr = p.communicate()
         
         # TODO: replace ugly hack bellow
+        # expecting that cmd is without .py
         if not cmdout and sys.platform == 'win32':
-            if os.path.splitext(cmd)[1] != '.py':
+            # we in fact expect pure module name (without extension)
+            # so, lets remove extension
+            if os.path.splitext(cmd)[1] == '.py':
+                cmd = cmd[:-3]
+            if cmd == 'd.rast3d':
                 cmd += '.py'
-            
-            if cmd == 'd.rast3d.py':
                 os.chdir(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'scripts'))
             else:
-                os.chdir(os.path.join(os.getenv('GISBASE'), 'scripts'))
+                cmd = get_real_command(cmd)
             p = Popen([sys.executable, cmd, '--interface-description'],
                       stdout = PIPE, stderr = PIPE)
             cmdout, cmderr = p.communicate()



More information about the grass-commit mailing list