]> git.openstreetmap.org Git - nominatim.git/blob - src/nominatim_db/tools/exec_utils.py
improve error message on missing modules from nominatim_api
[nominatim.git] / src / nominatim_db / tools / exec_utils.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Helper functions for executing external programs.
9 """
10 from typing import Any, Mapping, List, Optional
11 import logging
12 import os
13 import re
14 import subprocess
15 import shutil
16
17 from ..typing import StrPath
18 from ..db.connection import get_pg_env
19 from ..errors import UsageError
20 from ..version import OSM2PGSQL_REQUIRED_VERSION
21
22 LOG = logging.getLogger()
23
24 def run_php_server(server_address: str, base_dir: StrPath) -> None:
25     """ Run the built-in server from the given directory.
26     """
27     subprocess.run(['/usr/bin/env', 'php', '-S', server_address],
28                    cwd=str(base_dir), check=True)
29
30
31 def run_osm2pgsql(options: Mapping[str, Any]) -> None:
32     """ Run osm2pgsql with the given options.
33     """
34     _check_osm2pgsql_version(options['osm2pgsql'])
35
36     env = get_pg_env(options['dsn'])
37
38     cmd = [_find_osm2pgsql_cmd(options['osm2pgsql']),
39            '--append' if options['append'] else '--create',
40            '--slim',
41            '--log-progress', 'true',
42            '--number-processes', '1' if options['append'] else str(options['threads']),
43            '--cache', str(options['osm2pgsql_cache']),
44            '--style', str(options['osm2pgsql_style'])
45           ]
46
47     if str(options['osm2pgsql_style']).endswith('.lua'):
48         env['LUA_PATH'] = ';'.join((str(options['osm2pgsql_style_path'] / '?.lua'),
49                                     os.environ.get('LUAPATH', ';')))
50         cmd.extend(('--output', 'flex'))
51
52         for flavour in ('data', 'index'):
53             if options['tablespaces'][f"main_{flavour}"]:
54                 env[f"NOMINATIM_TABLESPACE_PLACE_{flavour.upper()}"] = \
55                     options['tablespaces'][f"main_{flavour}"]
56     else:
57         cmd.extend(('--output', 'gazetteer', '--hstore', '--latlon'))
58         cmd.extend(_mk_tablespace_options('main', options))
59
60
61     if options['flatnode_file']:
62         cmd.extend(('--flat-nodes', options['flatnode_file']))
63
64     cmd.extend(_mk_tablespace_options('slim', options))
65
66     if options.get('disable_jit', False):
67         env['PGOPTIONS'] = '-c jit=off -c max_parallel_workers_per_gather=0'
68
69     if 'import_data' in options:
70         cmd.extend(('-r', 'xml', '-'))
71     elif isinstance(options['import_file'], list):
72         for fname in options['import_file']:
73             cmd.append(str(fname))
74     else:
75         cmd.append(str(options['import_file']))
76
77     subprocess.run(cmd, cwd=options.get('cwd', '.'),
78                    input=options.get('import_data'),
79                    env=env, check=True)
80
81
82 def _mk_tablespace_options(ttype: str, options: Mapping[str, Any]) -> List[str]:
83     cmds: List[str] = []
84     for flavour in ('data', 'index'):
85         if options['tablespaces'][f"{ttype}_{flavour}"]:
86             cmds.extend((f"--tablespace-{ttype}-{flavour}",
87                          options['tablespaces'][f"{ttype}_{flavour}"]))
88
89     return cmds
90
91
92 def _find_osm2pgsql_cmd(cmdline: Optional[str]) -> str:
93     if cmdline is not None:
94         return cmdline
95
96     in_path = shutil.which('osm2pgsql')
97     if in_path is None:
98         raise UsageError('osm2pgsql executable not found. Please install osm2pgsql first.')
99
100     return str(in_path)
101
102
103 def _check_osm2pgsql_version(cmdline: Optional[str]) -> None:
104     cmd = [_find_osm2pgsql_cmd(cmdline), '--version']
105
106     result = subprocess.run(cmd, capture_output=True, check=True)
107
108     if not result.stderr:
109         raise UsageError("osm2pgsql does not print version information.")
110
111     verinfo = result.stderr.decode('UTF-8')
112
113     match = re.search(r'osm2pgsql version (\d+)\.(\d+)', verinfo)
114     if match is None:
115         raise UsageError(f"No version information found in output: {verinfo}")
116
117     if (int(match[1]), int(match[2])) < OSM2PGSQL_REQUIRED_VERSION:
118         raise UsageError(f"osm2pgsql is too old. Found version {match[1]}.{match[2]}. "
119                          f"Need at least version {'.'.join(map(str, OSM2PGSQL_REQUIRED_VERSION))}.")