1 # SPDX-License-Identifier: GPL-3.0-or-later
 
   3 # This file is part of Nominatim. (https://nominatim.org)
 
   5 # Copyright (C) 2025 by the Nominatim developer community.
 
   6 # For a full list of authors see the git log.
 
   8 Helper functions for executing external programs.
 
  10 from typing import Any, Mapping, List, Optional
 
  17 from ..db.connection import get_pg_env
 
  18 from ..errors import UsageError
 
  19 from ..version import OSM2PGSQL_REQUIRED_VERSION
 
  21 LOG = logging.getLogger()
 
  24 def run_osm2pgsql(options: Mapping[str, Any]) -> None:
 
  25     """ Run osm2pgsql with the given options.
 
  27     _check_osm2pgsql_version(options['osm2pgsql'])
 
  29     env = get_pg_env(options['dsn'])
 
  31     cmd = [_find_osm2pgsql_cmd(options['osm2pgsql']),
 
  32            '--append' if options['append'] else '--create',
 
  34            '--log-progress', 'true',
 
  35            '--number-processes', '1' if options['append'] else str(options['threads']),
 
  36            '--cache', str(options['osm2pgsql_cache']),
 
  37            '--style', str(options['osm2pgsql_style'])
 
  40     env['LUA_PATH'] = ';'.join((str(options['osm2pgsql_style_path'] / '?.lua'),
 
  41                                 os.environ.get('LUA_PATH', ';')))
 
  42     env['THEMEPARK_PATH'] = str(options['osm2pgsql_style_path'] / 'themes')
 
  43     if 'THEMEPARK_PATH' in os.environ:
 
  44         env['THEMEPARK_PATH'] += ':' + os.environ['THEMEPARK_PATH']
 
  45     cmd.extend(('--output', 'flex'))
 
  47     for flavour in ('data', 'index'):
 
  48         if options['tablespaces'][f"main_{flavour}"]:
 
  49             env[f"NOMINATIM_TABLESPACE_PLACE_{flavour.upper()}"] = \
 
  50                 options['tablespaces'][f"main_{flavour}"]
 
  52     if options['flatnode_file']:
 
  53         cmd.extend(('--flat-nodes', options['flatnode_file']))
 
  55     cmd.extend(_mk_tablespace_options('slim', options))
 
  57     if options.get('disable_jit', False):
 
  58         env['PGOPTIONS'] = '-c jit=off -c max_parallel_workers_per_gather=0'
 
  60     if 'import_data' in options:
 
  61         cmd.extend(('-r', 'xml', '-'))
 
  62     elif isinstance(options['import_file'], list):
 
  63         for fname in options['import_file']:
 
  64             cmd.append(str(fname))
 
  66         cmd.append(str(options['import_file']))
 
  68     subprocess.run(cmd, cwd=options.get('cwd', '.'),
 
  69                    input=options.get('import_data'),
 
  73 def _mk_tablespace_options(ttype: str, options: Mapping[str, Any]) -> List[str]:
 
  75     for flavour in ('data', 'index'):
 
  76         if options['tablespaces'][f"{ttype}_{flavour}"]:
 
  77             cmds.extend((f"--tablespace-{ttype}-{flavour}",
 
  78                          options['tablespaces'][f"{ttype}_{flavour}"]))
 
  83 def _find_osm2pgsql_cmd(cmdline: Optional[str]) -> str:
 
  87     in_path = shutil.which('osm2pgsql')
 
  89         raise UsageError('osm2pgsql executable not found. Please install osm2pgsql first.')
 
  94 def _check_osm2pgsql_version(cmdline: Optional[str]) -> None:
 
  95     cmd = [_find_osm2pgsql_cmd(cmdline), '--version']
 
  97     result = subprocess.run(cmd, capture_output=True, check=True)
 
 100         raise UsageError("osm2pgsql does not print version information.")
 
 102     verinfo = result.stderr.decode('UTF-8')
 
 104     match = re.search(r'osm2pgsql version (\d+)\.(\d+)', verinfo)
 
 106         raise UsageError(f"No version information found in output: {verinfo}")
 
 108     if (int(match[1]), int(match[2])) < OSM2PGSQL_REQUIRED_VERSION:
 
 109         raise UsageError(f"osm2pgsql is too old. Found version {match[1]}.{match[2]}. "
 
 110                          f"Need at least version {'.'.join(map(str, OSM2PGSQL_REQUIRED_VERSION))}.")