From 39da931a565c7f6e6c46b2d81ba320095c3191b9 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 19 Dec 2019 12:11:32 -0800 Subject: [PATCH] Look in Program Files for executables and liblept5.dll --- docs/installation.rst | 8 +++-- src/ocrmypdf/exec/__init__.py | 59 ++++++++++++++++++++++++++++++++++- src/ocrmypdf/leptonica.py | 2 ++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index c1186aa8..526cf0eb 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -487,9 +487,11 @@ Windows 10 64-bit and 64-bit versions of applications are recommended. Earlier versions of Windows and 32-bit versions of these programs are not tested, and not supported at this time. -Modify your ``PATH`` environment variable so that Tesseract and Ghostscript, and -any optional executables can be found. You can enter it in the command line -or `follow these directions `_ +OCRmyPDF will check for Tesseract-OCR and Ghostscript in your Program Files folder. +If they are in some other location, you may need to modify the ``PATH`` +environment variable so Tesseract, Ghostscript, and other any optional executables can +be found. You can enter it in the command line or +`follow these directions `_ to make the change persistent and system-wide. You may then use pip to install ocrmypdf: diff --git a/src/ocrmypdf/exec/__init__.py b/src/ocrmypdf/exec/__init__.py index d5a7de44..b571c333 100644 --- a/src/ocrmypdf/exec/__init__.py +++ b/src/ocrmypdf/exec/__init__.py @@ -23,6 +23,7 @@ import re import sys import shutil from collections.abc import Mapping +from functools import lru_cache from subprocess import PIPE, STDOUT, CalledProcessError, run as subprocess_run from ..exceptions import ExitCode, MissingDependencyError @@ -39,13 +40,40 @@ def _get_program(args, env=None): def run(args, *, env=None, **kwargs): + """Wrapper around subprocess.run() + + The main purpose of this wrapper is to allow us to substitute the main program + for a spoof in the test suite. The hidden variable _OCRMYPDF_TEST_PATH replaces + the main PATH as a location to check for programs to run. + + Secondly we have to account for behavioral differences in Windows in particular. + Creating symbolic links in Windows requires administrator privileges and + may not work if for some reason we're using a FAT file system or the temporary + folder is on a different drive from the working folder. The test suite + works around this by creating shim Python scripts that perform the same function + as a symbolic link, but those shims require support on this side, to ensure + we call them with Python. + + """ if not env: env = os.environ + + # Search in spoof path if necessary program = _get_program(args, env) + + # If we are running a .py on Windows, ensure we call it with this Python + # (to support test suite shims) if os.name == 'nt' and program.lower().endswith('.py'): args = [sys.executable, program] + args[1:] else: args = [program] + args[1:] + + if os.name == 'nt' and not shutil.which(args[0], path=os.get_exec_path(env)): + shimmed_path = shim_paths_with_program_files(env) + new_args0 = shutil.which(args[0], path=shimmed_path) + if new_args0: + args[0] = new_args0 + log.debug(args) if sys.version_info < (3, 7) and os.name == 'nt': # Can't use close_fds=True on Windows with Python 3.6 or older @@ -55,7 +83,7 @@ def run(args, *, env=None, **kwargs): def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)', env=None): - "Get the version of the specified program" + """Get the version of the specified program""" args_prog = [program, version_arg] try: proc = run( @@ -91,6 +119,35 @@ def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)', env return version +@lru_cache(maxsize=1) +def shim_paths_with_program_files(env=None): + if not env: + env = os.environ + program_files = env.get('PROGRAMFILES', '') + if not program_files: + return env.get('PATH', '') + paths = [] + try: + for dirname in os.listdir(program_files): + if dirname.lower() == 'tesseract-ocr': + paths.append(os.path.join(program_files, dirname)) + if dirname.lower() == 'gs': + try: + latest_gs = max( + os.listdir(os.path.join(program_files, dirname)), + key=lambda d: float(d[2:]), + ) + except (FileNotFoundError, NotADirectoryError): + continue + paths.append(os.path.join(program_files, dirname, latest_gs, 'bin')) + except EnvironmentError: + pass + paths.extend( + path for path in os.environ['PATH'].split(os.pathsep) if path not in set(paths) + ) + return os.pathsep.join(paths) + + missing_program = ''' The program '{program}' could not be executed or was not found on your system PATH. diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index a4dec30f..344c2d9e 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -35,6 +35,7 @@ from tempfile import TemporaryFile from .lib._leptonica import ffi from .exceptions import MissingDependencyError +from .exec import shim_paths_with_program_files # pylint: disable=protected-access @@ -42,6 +43,7 @@ logger = logging.getLogger(__name__) if os.name == 'nt': libname = 'liblept-5' + os.environ['PATH'] = shim_paths_with_program_files() else: libname = 'lept' _libpath = find_library(libname)