source: grass/trunk/lib/init/grass.py

Last change on this file was 74475, checked in by neteler, 5 years ago

grass.py: catch empty GISBASE env var

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-python
File size: 82.2 KB
Line 
1#!/usr/bin/env python
2#############################################################################
3#
4# MODULE: GRASS initialization script
5# AUTHOR(S): Original author unknown - probably CERL
6# Andreas Lange <andreas.lange rhein-main.de>
7# Huidae Cho <grass4u gmail.com>
8# Justin Hickey <jhickey hpcc.nectec.or.th>
9# Markus Neteler <neteler osgeo.org>
10# Hamish Bowman <hamish_b yahoo.com>
11#
12# GRASS 7: converted to Python (based on init.sh shell
13# script from GRASS 6) by Glynn Clements
14# Martin Landa <landa.martin gmail.com>
15# Luca Delucchi <lucadeluge gmail.com>
16# Vaclav Petras <wenzeslaus gmail.com> (refactoring and exec)
17# PURPOSE: Sets up environment variables, parses any remaining
18# command line options for setting the GISDBASE, LOCATION,
19# and/or MAPSET. Finally it starts GRASS with the appropriate
20# user interface and cleans up after it is finished.
21# COPYRIGHT: (C) 2000-2019 by the GRASS Development Team
22#
23# This program is free software under the GNU General
24# Public License (>=v2). Read the file COPYING that
25# comes with GRASS for details.
26#
27#############################################################################
28
29"""
30Script to run GRASS session.
31
32Some of the functions could be used separately but import from this module
33is not safe, i.e. it has side effects (this should be changed in the future).
34"""
35
36# we allow for long file because we want to avoid imports if possible
37# (this makes it more stable since we have to set up paths first)
38# pylint: disable=too-many-lines
39
40from __future__ import print_function
41import sys
42import os
43import atexit
44import string
45import subprocess
46import re
47import platform
48import tempfile
49import locale
50
51
52# ----+- Python 3 compatibility start -+----
53PY2 = sys.version[0] == '2'
54ENCODING = locale.getdefaultlocale()[1]
55if ENCODING is None:
56 ENCODING = 'UTF-8'
57 print("Default locale not found, using UTF-8") # intentionally not translatable
58
59
60def decode(bytes_, encoding=None):
61 """Decode bytes with default locale and return (unicode) string
62 Adapted from lib/python/core/utils.py
63
64 No-op if parameter is not bytes (assumed unicode string).
65
66 :param bytes bytes_: the bytes to decode
67 :param encoding: encoding to be used, default value is None
68 """
69 if sys.version_info.major >= 3:
70 unicode = str
71 if isinstance(bytes_, unicode):
72 return bytes_
73 elif isinstance(bytes_, bytes):
74 if encoding is None:
75 enc = ENCODING
76 else:
77 enc = encoding
78 return bytes_.decode(enc)
79 else:
80 # if something else than text
81 raise TypeError("can only accept types str and bytes")
82
83
84def encode(string, encoding=None):
85 """Encode string with default locale and return bytes with that encoding
86 Adapted from lib/python/core/utils.py
87
88 No-op if parameter is bytes (assumed already encoded).
89 This ensures garbage in, garbage out.
90
91 :param str string: the string to encode
92 :param encoding: encoding to be used, default value is None
93 """
94 if sys.version_info.major >= 3:
95 unicode = str
96 if isinstance(string, bytes):
97 return string
98 # this also tests str in Py3:
99 elif isinstance(string, unicode):
100 if encoding is None:
101 enc = ENCODING
102 else:
103 enc = encoding
104 return string.encode(enc)
105 else:
106 # if something else than text
107 raise TypeError("can only accept types str and bytes")
108
109
110# currently not used, see https://trac.osgeo.org/grass/ticket/3508
111def to_text_string(obj, encoding=ENCODING):
112 """Convert `obj` to (unicode) text string"""
113 if PY2:
114 # Python 2
115 return encode(obj, encoding=encoding)
116 else:
117 # Python 3
118 return decode(obj, encoding=encoding)
119
120
121if PY2:
122 import types
123 string_types = basestring,
124 integer_types = (int, long)
125 class_types = (type, types.ClassType)
126 text_type = unicode
127 binary_type = str
128else:
129 string_types = str,
130 integer_types = int,
131 class_types = type,
132 text_type = str
133 binary_type = bytes
134 MAXSIZE = sys.maxsize
135
136# ----+- Python 3 compatibility end -+----
137
138# Variables substituted during build process
139if 'GISBASE' in os.environ and len(os.getenv('GISBASE')) > 0:
140 # TODO: should this be something like GRASS_PATH?
141 # GISBASE marks complete runtime, so no need to get it here when
142 # setting it up, possible scenario: existing runtime and starting
143 # GRASS in that, we want to overwrite the settings, not to take it
144 # possibly same for GRASS_PROJSHARE and others but maybe not
145 gisbase = os.environ['GISBASE']
146else:
147 gisbase = "@GISBASE@"
148cmd_name = "@START_UP@"
149grass_version = "@GRASS_VERSION_NUMBER@"
150ld_library_path_var = '@LD_LIBRARY_PATH_VAR@'
151if 'GRASS_PROJSHARE' in os.environ:
152 config_projshare = os.environ['GRASS_PROJSHARE']
153else:
154 config_projshare = "@CONFIG_PROJSHARE@"
155
156gisbase = os.path.normpath(gisbase)
157
158# i18N
159import gettext
160# TODO: is this needed or even desirable when we have set_language()?
161gettext.install('grasslibs', os.path.join(gisbase, 'locale'))
162
163
164def warning(text):
165 sys.stderr.write(_("WARNING") + ': ' + text + os.linesep)
166
167
168def try_remove(path):
169 try:
170 os.remove(path)
171 except:
172 pass
173
174
175def try_rmdir(path):
176 try:
177 os.rmdir(path)
178 except:
179 pass
180
181
182def clean_env(gisrc):
183 env_curr = read_gisrc(gisrc)
184 env_new = {}
185 for k, v in env_curr.items():
186 if k.endswith('PID') or k.startswith('MONITOR'):
187 continue
188 env_new[k] = v
189
190 write_gisrc(env_new, gisrc)
191
192
193def cleanup_dir(path):
194 if not path:
195 return
196
197 for root, dirs, files in os.walk(path, topdown=False):
198 for name in files:
199 try_remove(os.path.join(root, name))
200 for name in dirs:
201 try_rmdir(os.path.join(root, name))
202
203
204class Cleaner(object): # pylint: disable=R0903
205 """Holds directories and files which needs to be cleaned or deleted"""
206 def __init__(self):
207 self.tmpdir = None
208
209 def cleanup(self):
210 """This can be registered with atexit
211
212 Object can then still change and add or remove directories to clean"""
213 # all exits after setting up tmp dirs (system/location) should
214 # also tidy it up
215 cleanup_dir(self.tmpdir)
216 try_rmdir(self.tmpdir)
217
218
219def fatal(msg):
220 sys.stderr.write("%s: " % _('ERROR') + msg + os.linesep)
221 sys.exit(_("Exiting..."))
222
223
224def message(msg):
225 sys.stderr.write(msg + "\n")
226 sys.stderr.flush()
227
228
229# mechanism meant for debugging this script (only)
230# private global to store if we are debugging
231_DEBUG = None
232
233
234def is_debug():
235 """Returns True if we are in debug mode
236
237 For debug messages use ``debug()``.
238 """
239 global _DEBUG
240 if _DEBUG is not None:
241 return _DEBUG
242 _DEBUG = os.getenv('GRASS_DEBUG')
243 # translate to bool (no or empty variable means false)
244 if _DEBUG:
245 _DEBUG = True
246 else:
247 _DEBUG = False
248 return _DEBUG
249
250
251def debug(msg):
252 """Print a debug message if in debug mode
253
254 Do not use translatable strings for debug messages.
255 """
256 if is_debug():
257 sys.stderr.write("DEBUG: %s\n" % msg)
258 sys.stderr.flush()
259
260
261def readfile(path):
262 debug("Reading %s" % path)
263 f = open(path, 'r')
264 s = f.read()
265 f.close()
266 return s
267
268
269def writefile(path, s):
270 debug("Writing %s" % path)
271 f = open(path, 'w')
272 f.write(s)
273 f.close()
274
275
276def call(cmd, **kwargs):
277 """Wrapper for subprocess.call to deal with platform-specific issues"""
278 if windows:
279 kwargs['shell'] = True
280 return subprocess.call(cmd, **kwargs)
281
282
283def Popen(cmd, **kwargs): # pylint: disable=C0103
284 """Wrapper for subprocess.Popen to deal with platform-specific issues"""
285 if windows:
286 kwargs['shell'] = True
287 return subprocess.Popen(cmd, **kwargs)
288
289
290def gpath(*args):
291 """Costruct path to file or directory in GRASS GIS installation
292
293 Can be called only after gisbase was set.
294 """
295 return os.path.join(gisbase, *args)
296
297
298# for wxpath
299_WXPYTHON_BASE = None
300
301
302def wxpath(*args):
303 """Costruct path to file or directory in GRASS wxGUI
304
305 Can be called only after gisbase was set.
306
307 This function does not check if the directories exist or if GUI works
308 this must be done by the caller if needed.
309 """
310 global _WXPYTHON_BASE
311 if not _WXPYTHON_BASE:
312 # this can be called only after gisbase was set
313 _WXPYTHON_BASE = gpath("gui", "wxpython")
314 return os.path.join(_WXPYTHON_BASE, *args)
315
316
317# using format for most but leaving usage of template for the dynamic ones
318# two different methods are easy way to implement two phase construction
319help_text = r"""GRASS GIS $VERSION_NUMBER
320Geographic Resources Analysis Support System (GRASS GIS).
321
322{usage}:
323 $CMD_NAME [-h | --help] [-v | --version]
324 [-c | -c geofile | -c EPSG:code[:datum_trans] | -c XY]
325 [-e] [-f] [--text | --gtext | --gui] [--config param]
326 [[[GISDBASE/]LOCATION/]MAPSET]
327 $CMD_NAME [FLAG]... GISDBASE/LOCATION/MAPSET --exec EXECUTABLE [EPARAM]...
328 $CMD_NAME --tmp-location [geofile | EPSG | XY] --exec EXECUTABLE [EPARAM]...
329
330{flags}:
331 -h or --help {help_flag}
332 -v or --version {version_flag}
333 -c {create}
334 -e {exit_after}
335 -f {force_removal}
336 --text {text}
337 {text_detail}
338 --gtext {gtext}
339 {gtext_detail}
340 --gui {gui}
341 {gui_detail}
342 --config {config}
343 {config_detail}
344 --exec EXECUTABLE {exec_}
345 {exec_detail}
346 --tmp-location {tmp_location}
347
348{params}:
349 GISDBASE {gisdbase}
350 {gisdbase_detail}
351 LOCATION {location}
352 {location_detail}
353 MAPSET {mapset}
354
355 GISDBASE/LOCATION/MAPSET {full_mapset}
356
357 EXECUTABLE {executable}
358 EPARAM {executable_params}
359 FLAG {standard_flags}
360
361{env_vars}:
362 GRASS_GUI {gui_var}
363 GRASS_HTML_BROWSER {html_var}
364 GRASS_ADDON_PATH {addon_path_var}
365 GRASS_ADDON_BASE {addon_base_var}
366 GRASS_BATCH_JOB {batch_var}
367 GRASS_PYTHON {python_var}
368""".format(
369 usage=_("Usage"),
370 flags=_("Flags"),
371 help_flag=_("print this help message"),
372 version_flag=_("show version information and exit"),
373 create=_("create given database, location or mapset if it doesn't exist"),
374 exit_after=_("exit after creation of location or mapset. Only with -c flag"),
375 force_removal=_("force removal of .gislock if exists (use with care!). Only with --text flag"),
376 text=_("use text based interface (skip graphical welcome screen)"),
377 text_detail=_("and set as default"),
378 gtext=_("use text based interface (show graphical welcome screen)"),
379 gtext_detail=_("and set as default"),
380 gui=_("use $DEFAULT_GUI graphical user interface"),
381 gui_detail=_("and set as default"),
382 config=_("print GRASS configuration parameters"),
383 config_detail=_("options: arch,build,compiler,path,revision,svn_revision,version"),
384 params=_("Parameters"),
385 gisdbase=_("initial GRASS database directory"),
386 gisdbase_detail=_("directory containing Locations"),
387 location=_("initial GRASS Location"),
388 location_detail=_("directory containing Mapsets with one common coordinate system (projection)"),
389 mapset=_("initial GRASS Mapset"),
390 full_mapset=_("fully qualified initial Mapset directory"),
391 env_vars=_("Environment variables relevant for startup"),
392 gui_var=_("select GUI (text, gui, gtext)"),
393 html_var=_("set html web browser for help pages"),
394 addon_path_var=_("set additional path(s) to local GRASS modules or user scripts"),
395 addon_base_var=_("set additional GISBASE for locally installed GRASS Addons"),
396 batch_var=_("shell script to be processed as batch job"),
397 python_var=_("set Python interpreter name to override 'python'"),
398 exec_=_("execute GRASS module or script"),
399 exec_detail=_("provided executable will be executed in GRASS session"),
400 executable=_("GRASS module, script or any other executable"),
401 executable_params=_("parameters of the executable"),
402 standard_flags=_("standard flags"),
403 tmp_location=_("create temporary location (use with the --exec flag)"),
404 )
405
406
407def help_message(default_gui):
408 t = string.Template(help_text)
409 s = t.substitute(CMD_NAME=cmd_name, DEFAULT_GUI=default_gui,
410 VERSION_NUMBER=grass_version)
411 sys.stderr.write(s)
412
413
414def get_grass_config_dir():
415 """Get configuration directory
416
417 Determines path of GRASS GIS user configuration directory and creates
418 it if it does not exist.
419
420 Configuration directory is for example used for grass env file
421 (the one which caries mapset settings from session to session).
422 """
423 if sys.platform == 'win32':
424 grass_config_dirname = "GRASS7"
425 win_conf_path = os.getenv('APPDATA')
426 # this can happen with some strange settings
427 if not win_conf_path:
428 fatal(_("The APPDATA variable is not set, ask your operating"
429 " system support"))
430 if not os.path.exists(win_conf_path):
431 fatal(_("The APPDATA variable points to directory which does"
432 " not exist, ask your operating system support"))
433 directory = os.path.join(win_conf_path, grass_config_dirname)
434 else:
435 grass_config_dirname = ".grass7"
436 directory = os.path.join(os.getenv('HOME'), grass_config_dirname)
437 if not os.path.exists(directory):
438 try:
439 os.mkdir(directory)
440 except OSError as e:
441 # Can happen as a race condition
442 if not e.errno == 17:
443 fatal(
444 _("Failed to create configuration directory '%s' with error: %s")
445 % (directory, e.strerror))
446 return directory
447
448
449def create_tmp(user, gis_lock):
450 """Create temporary directory
451
452 :param user: user name to be used in the directory name
453 :param gis_lock: session lock filename to be used in the directory name
454 """
455 # use $TMPDIR if it exists, then $TEMP, otherwise /tmp
456 tmp = os.getenv('TMPDIR')
457 if not tmp:
458 tmp = os.getenv('TEMP')
459 if not tmp:
460 tmp = os.getenv('TMP')
461 if not tmp:
462 tmp = tempfile.gettempdir()
463
464 if tmp:
465 tmpdir = os.path.join(
466 tmp, "grass7-%(user)s-%(lock)s" % {'user': user,
467 'lock': gis_lock})
468 try:
469 os.mkdir(tmpdir, 0o700)
470 except:
471 tmp = None
472
473 if not tmp:
474 for ttmp in ("/tmp", "/var/tmp", "/usr/tmp"):
475 tmp = ttmp
476 tmpdir = os.path.join(
477 tmp, "grass7-%(user)s-%(lock)s" % {'user': user,
478 'lock': gis_lock})
479 try:
480 os.mkdir(tmpdir, 0o700)
481 except:
482 tmp = None
483 if tmp:
484 break
485
486 if not tmp:
487 fatal(_("Unable to create temporary directory <grass7-%(user)s-"
488 "%(lock)s>! Exiting.") % {'user': user, 'lock': gis_lock})
489
490 # promoting the variable even if it was not defined before
491 os.environ['TMPDIR'] = tmpdir
492
493 debug("Tmp directory '{tmpdir}' created for user '{user}'".format(
494 tmpdir=tmpdir, user=user))
495 return tmpdir
496
497
498def get_gisrc_from_config_dir(grass_config_dir, batch_job):
499 """Set the global grassrc file (aka grassrcrc)"""
500 if batch_job:
501 # use individual GISRCRC files when in batch mode (r33174)
502 filename = os.path.join(grass_config_dir, "rc.%s" % platform.node())
503 if os.access(filename, os.R_OK):
504 return filename
505 # use standard file if in normal mode or the special file is not available
506 return os.path.join(grass_config_dir, "rc")
507
508
509def create_gisrc(tmpdir, gisrcrc):
510 # Set the session grassrc file
511 gisrc = os.path.join(tmpdir, "gisrc")
512 os.environ['GISRC'] = gisrc
513
514 # remove invalid GISRC file to avoid disturbing error messages:
515 try:
516 s = readfile(gisrcrc)
517 if "UNKNOWN" in s:
518 try_remove(gisrcrc)
519 s = None
520 except:
521 s = None
522
523 # Copy the global grassrc file to the session grassrc file
524 if s:
525 writefile(gisrc, s)
526 return gisrc
527
528
529def read_gisrc(filename):
530 kv = {}
531 try:
532 f = open(filename, 'r')
533 except IOError:
534 return kv
535
536 for line in f:
537 try:
538 k, v = line.split(':', 1)
539 except ValueError as e:
540 warning(_("Invalid line in RC file ({file}):"
541 " '{line}' ({error})\n").format(
542 line=line, error=e, file=filename))
543 continue
544 kv[k.strip()] = v.strip()
545 if not kv:
546 warning(_("Empty RC file ({file})").format(file=filename))
547 f.close()
548
549 return kv
550
551
552def read_env_file(path):
553 kv = {}
554 f = open(path, 'r')
555 for line in f:
556 k, v = line.split(':', 1)
557 kv[k.strip()] = v.strip()
558 f.close()
559 return kv
560
561
562def write_gisrc(kv, filename):
563 f = open(filename, 'w')
564 for k, v in kv.items():
565 f.write("%s: %s\n" % (k, v))
566 f.close()
567
568
569def read_gui(gisrc, default_gui):
570 grass_gui = None
571 # At this point the GRASS user interface variable has been set from the
572 # command line, been set from an external environment variable,
573 # or is not set. So we check if it is not set
574 # Check for a reference to the GRASS user interface in the grassrc file
575 if os.access(gisrc, os.R_OK):
576 kv = read_gisrc(gisrc)
577 if 'GRASS_GUI' in os.environ:
578 grass_gui = os.environ['GRASS_GUI']
579 elif 'GUI' in kv:
580 grass_gui = kv['GUI']
581 elif 'GRASS_GUI' in kv:
582 # For backward compatibility (GRASS_GUI renamed to GUI)
583 grass_gui = kv['GRASS_GUI']
584 else:
585 # Set the GRASS user interface to the default if needed
586 grass_gui = default_gui
587
588 if not grass_gui:
589 grass_gui = default_gui
590
591 if grass_gui == 'gui':
592 grass_gui = default_gui
593
594 # FIXME oldtcltk, gis.m, d.m no longer exist (remove this around 7.2)
595 if grass_gui in ['d.m', 'gis.m', 'oldtcltk', 'tcltk']:
596 warning(_("GUI <%s> not supported in this version") % grass_gui)
597 grass_gui = default_gui
598
599 return grass_gui
600
601
602def path_prepend(directory, var):
603 path = os.getenv(var)
604 if path:
605 path = directory + os.pathsep + path
606 else:
607 path = directory
608 os.environ[var] = path
609
610
611def path_append(directory, var):
612 path = os.getenv(var)
613 if path:
614 path = path + os.pathsep + directory
615 else:
616 path = directory
617 os.environ[var] = path
618
619
620def set_paths(grass_config_dir):
621 # addons (path)
622 addon_path = os.getenv('GRASS_ADDON_PATH')
623 if addon_path:
624 for path in addon_path.split(os.pathsep):
625 path_prepend(addon_path, 'PATH')
626
627 # addons (base)
628 addon_base = os.getenv('GRASS_ADDON_BASE')
629 if not addon_base:
630 addon_base = os.path.join(grass_config_dir, 'addons')
631 os.environ['GRASS_ADDON_BASE'] = addon_base
632 if not windows:
633 path_prepend(os.path.join(addon_base, 'scripts'), 'PATH')
634 path_prepend(os.path.join(addon_base, 'bin'), 'PATH')
635
636 # standard installation
637 if not windows:
638 path_prepend(gpath('scripts'), 'PATH')
639 path_prepend(gpath('bin'), 'PATH')
640
641 # Set PYTHONPATH to find GRASS Python modules
642 if os.path.exists(gpath('etc', 'python')):
643 pythonpath = gpath('etc', 'python')
644 path_prepend(pythonpath, 'PYTHONPATH')
645 # the env var PYTHONPATH is only evaluated when python is started,
646 # thus:
647 sys.path.append(pythonpath)
648 # now we can import stuff from GRASS lib/python
649
650 # set path for the GRASS man pages
651 grass_man_path = gpath('docs', 'man')
652 addons_man_path = os.path.join(addon_base, 'docs', 'man')
653 man_path = os.getenv('MANPATH')
654 sys_man_path = None
655 if man_path:
656 path_prepend(addons_man_path, 'MANPATH')
657 path_prepend(grass_man_path, 'MANPATH')
658 else:
659 try:
660 nul = open(os.devnull, 'w')
661 p = Popen(['manpath'], stdout=subprocess.PIPE, stderr=nul)
662 nul.close()
663 s = p.stdout.read()
664 p.wait()
665 sys_man_path = s.strip()
666 except:
667 pass
668
669 if sys_man_path:
670 os.environ['MANPATH'] = to_text_string(sys_man_path)
671 path_prepend(addons_man_path, 'MANPATH')
672 path_prepend(grass_man_path, 'MANPATH')
673 else:
674 os.environ['MANPATH'] = to_text_string(addons_man_path)
675 path_prepend(grass_man_path, 'MANPATH')
676
677 # Set LD_LIBRARY_PATH (etc) to find GRASS shared libraries
678 # this works for subprocesses but won't affect the current process
679 path_prepend(gpath("lib"), ld_library_path_var)
680
681
682def find_exe(pgm):
683 for directory in os.getenv('PATH').split(os.pathsep):
684 path = os.path.join(directory, pgm)
685 if os.access(path, os.X_OK):
686 return path
687 return None
688
689
690def set_defaults():
691 # GRASS_PAGER
692 if not os.getenv('GRASS_PAGER'):
693 if find_exe("more"):
694 pager = "more"
695 elif find_exe("less"):
696 pager = "less"
697 elif windows:
698 pager = "more"
699 else:
700 pager = "cat"
701 os.environ['GRASS_PAGER'] = pager
702
703 # GRASS_PYTHON
704 if not os.getenv('GRASS_PYTHON'):
705 if windows:
706 os.environ['GRASS_PYTHON'] = "python.exe"
707 else:
708 os.environ['GRASS_PYTHON'] = "python"
709
710 # GRASS_GNUPLOT
711 if not os.getenv('GRASS_GNUPLOT'):
712 os.environ['GRASS_GNUPLOT'] = "gnuplot -persist"
713
714 # GRASS_PROJSHARE
715 if not os.getenv('GRASS_PROJSHARE'):
716 os.environ['GRASS_PROJSHARE'] = config_projshare
717
718
719def set_display_defaults():
720 """ Predefine monitor size for certain architectures"""
721 if os.getenv('HOSTTYPE') == 'arm':
722 # small monitor on ARM (iPAQ, zaurus... etc)
723 os.environ['GRASS_RENDER_HEIGHT'] = "320"
724 os.environ['GRASS_RENDER_WIDTH'] = "240"
725
726
727def set_browser():
728 # GRASS_HTML_BROWSER
729 browser = os.getenv('GRASS_HTML_BROWSER')
730 if not browser:
731 if macosx:
732 # OSX doesn't execute browsers from the shell PATH - route through a
733 # script
734 browser = gpath('etc', "html_browser_mac.sh")
735 os.environ['GRASS_HTML_BROWSER_MACOSX'] = "-b com.apple.helpviewer"
736
737 if windows:
738 browser = "start"
739 elif cygwin:
740 browser = "explorer"
741 else:
742 # the usual suspects
743 browsers = ["xdg-open", "x-www-browser", "htmlview", "konqueror", "mozilla",
744 "mozilla-firefox", "firefox", "iceweasel", "opera",
745 "netscape", "dillo", "lynx", "links", "w3c"]
746 for b in browsers:
747 if find_exe(b):
748 browser = b
749 break
750
751 elif macosx:
752 # OSX doesn't execute browsers from the shell PATH - route through a
753 # script
754 os.environ['GRASS_HTML_BROWSER_MACOSX'] = "-b %s" % browser
755 browser = gpath('etc', "html_browser_mac.sh")
756
757 if not browser:
758 # even so we set to 'xdg-open' as a generic fallback
759 browser = "xdg-open"
760
761 os.environ['GRASS_HTML_BROWSER'] = browser
762
763
764def ensure_home():
765 """Set HOME if not set on MS Windows"""
766 if windows and not os.getenv('HOME'):
767 os.environ['HOME'] = os.path.join(os.getenv('HOMEDRIVE'),
768 os.getenv('HOMEPATH'))
769
770
771def create_initial_gisrc(filename):
772 # for convenience, define GISDBASE as pwd:
773 s = r"""GISDBASE: %s
774LOCATION_NAME: <UNKNOWN>
775MAPSET: <UNKNOWN>
776""" % os.getcwd()
777 writefile(filename, s)
778
779
780def check_gui(expected_gui):
781 grass_gui = expected_gui
782 # Check if we are running X windows by checking the DISPLAY variable
783 if os.getenv('DISPLAY') or windows or macosx:
784 # Check if python is working properly
785 if expected_gui in ('wxpython', 'gtext'):
786 nul = open(os.devnull, 'w')
787 p = Popen([os.environ['GRASS_PYTHON']], stdin=subprocess.PIPE,
788 stdout=nul, stderr=nul)
789 nul.close()
790 p.stdin.write("variable=True".encode(ENCODING))
791 p.stdin.close()
792 p.wait()
793 msg = None
794 if p.returncode != 0:
795 # Python was not found - switch to text interface mode
796 msg = _("The python command does not work as expected!\n"
797 "Please check your GRASS_PYTHON environment variable.\n"
798 "Use the -help option for details.\n")
799 if not os.path.exists(wxpath("wxgui.py")):
800 msg = _("GRASS GUI not found. Please check your installation.")
801 if msg:
802 warning(_("{}\nSwitching to text based interface mode.\n\n"
803 "Hit RETURN to continue.\n").format(msg))
804 sys.stdin.readline()
805 grass_gui = 'text'
806
807 else:
808 # Display a message if a graphical interface was expected
809 if expected_gui != 'text':
810 # Set the interface mode to text
811 warning(_("It appears that the X Windows system is not active.\n"
812 "A graphical based user interface is not supported.\n"
813 "(DISPLAY variable is not set.)\n"
814 "Switching to text based interface mode.\n\n"
815 "Hit RETURN to continue.\n"))
816 sys.stdin.readline()
817 grass_gui = 'text'
818 return grass_gui
819
820
821def save_gui(gisrc, grass_gui):
822 """Save the user interface variable in the grassrc file"""
823 if os.access(gisrc, os.F_OK):
824 kv = read_gisrc(gisrc)
825 kv['GUI'] = grass_gui
826 write_gisrc(kv, gisrc)
827
828
829def create_location(gisdbase, location, geostring):
830 """Create GRASS Location using georeferenced file or EPSG
831
832 EPSG code format is ``EPSG:code`` or ``EPSG:code:datum_trans``.
833
834 :param gisdbase: Path to GRASS GIS database directory
835 :param location: name of new Location
836 :param geostring: path to a georeferenced file or EPSG code
837 """
838 if gpath('etc', 'python') not in sys.path:
839 sys.path.append(gpath('etc', 'python'))
840 from grass.script import core as gcore # pylint: disable=E0611
841
842 try:
843 if geostring and geostring.upper().find('EPSG:') > -1:
844 # create location using EPSG code
845 epsg = geostring.split(':', 1)[1]
846 if ':' in epsg:
847 epsg, datum_trans = epsg.split(':', 1)
848 else:
849 datum_trans = None
850 gcore.create_location(gisdbase, location,
851 epsg=epsg, datum_trans=datum_trans)
852 elif geostring == 'XY':
853 # create an XY location
854 gcore.create_location(gisdbase, location)
855 else:
856 # create location using georeferenced file
857 gcore.create_location(gisdbase, location,
858 filename=geostring)
859 except gcore.ScriptError as err:
860 fatal(err.value.strip('"').strip("'").replace('\\n', os.linesep))
861
862
863# TODO: distinguish between valid for getting maps and usable as current
864# https://lists.osgeo.org/pipermail/grass-dev/2016-September/082317.html
865# interface created according to the current usage
866def is_mapset_valid(full_mapset):
867 """Return True if GRASS Mapset is valid"""
868 # WIND is created from DEFAULT_WIND by `g.region -d` and functions
869 # or modules which create a new mapset. Most modules will fail if
870 # WIND doesn't exist (assuming that neither GRASS_REGION nor
871 # WIND_OVERRIDE environmental variables are set).
872 return os.access(os.path.join(full_mapset, "WIND"), os.R_OK)
873
874
875def is_location_valid(gisdbase, location):
876 """Return True if GRASS Location is valid
877
878 :param gisdbase: Path to GRASS GIS database directory
879 :param location: name of a Location
880 """
881 # DEFAULT_WIND file should not be required until you do something
882 # that actually uses them. The check is just a heuristic; a directory
883 # containing a PERMANENT/DEFAULT_WIND file is probably a GRASS
884 # location, while a directory lacking it probably isn't.
885 return os.access(os.path.join(gisdbase, location,
886 "PERMANENT", "DEFAULT_WIND"), os.F_OK)
887
888
889# basically checking location, possibly split into two functions
890# (mapset one can call location one)
891def get_mapset_invalid_reason(gisdbase, location, mapset):
892 """Returns a message describing what is wrong with the Mapset
893
894 The goal is to provide the most suitable error message
895 (rather than to do a quick check).
896
897 :param gisdbase: Path to GRASS GIS database directory
898 :param location: name of a Location
899 :param mapset: name of a Mapset
900 :returns: translated message
901 """
902 full_location = os.path.join(gisdbase, location)
903 full_permanent = os.path.join(full_location, 'PERMANENT')
904 full_mapset = os.path.join(full_location, mapset)
905 # first checking the location validity
906 if not os.path.exists(full_location):
907 return _("Location <%s> doesn't exist") % full_location
908 elif 'PERMANENT' not in os.listdir(full_location):
909 return _("<%s> is not a valid GRASS Location"
910 " because PERMANENT Mapset is missing") % full_location
911 elif not os.path.isdir(full_permanent):
912 return _("<%s> is not a valid GRASS Location"
913 " because PERMANENT is not a directory") % full_location
914 # partially based on the is_location_valid() function
915 elif not os.path.isfile(os.path.join(full_permanent,
916 'DEFAULT_WIND')):
917 return _("<%s> is not a valid GRASS Location"
918 " because PERMANENT Mapset does not have a DEFAULT_WIND file"
919 " (default computational region)") % full_location
920 # if location is valid, check mapset
921 elif mapset not in os.listdir(full_location):
922 return _("Mapset <{mapset}> doesn't exist in GRASS Location <{loc}>. "
923 "A new mapset can be created by '-c' switch.").format(
924 mapset=mapset, loc=location)
925 elif not os.path.isdir(full_mapset):
926 return _("<%s> is not a GRASS Mapset"
927 " because it is not a directory") % mapset
928 elif not os.path.isfile(os.path.join(full_mapset, 'WIND')):
929 return _("<%s> is not a valid GRASS Mapset"
930 " because it does not have a WIND file") % mapset
931 # based on the is_mapset_valid() function
932 elif not os.access(os.path.join(full_mapset, "WIND"), os.R_OK):
933 return _("<%s> is not a valid GRASS Mapset"
934 " because its WIND file is not readable") % mapset
935 else:
936 return _("Mapset <{mapset}> or Location <{location}> is"
937 " invalid for an unknown reason").format(
938 mapset=mapset, loc=location)
939
940
941def can_create_location(gisdbase, location):
942 """Checks if location can be created"""
943 path = os.path.join(gisdbase, location)
944 if os.path.exists(path):
945 return False
946 return True
947
948
949def cannot_create_location_reason(gisdbase, location):
950 """Returns a message describing why location cannot be created
951
952 The goal is to provide the most suitable error message
953 (rather than to do a quick check).
954
955 :param gisdbase: Path to GRASS GIS database directory
956 :param location: name of a Location
957 :returns: translated message
958 """
959 path = os.path.join(gisdbase, location)
960 if is_location_valid(gisdbase, location):
961 return _("Unable to create new location because"
962 " the location <{location}>"
963 " already exists.").format(**locals())
964 elif os.path.isfile(path):
965 return _("Unable to create new location <{location}> because"
966 " <{path}> is a file.").format(**locals())
967 elif os.path.isdir(path):
968 return _("Unable to create new location <{location}> because"
969 " the directory <{path}>"
970 " already exists.").format(**locals())
971 else:
972 return _("Unable to create new location in"
973 " the directory <{path}>"
974 " for an unknown reason.").format(**locals())
975
976
977def set_mapset(gisrc, arg=None, geofile=None, create_new=False,
978 tmp_location=False, tmpdir=None):
979 """Selected Location and Mapset are checked and created if requested
980
981 The gisrc (GRASS environment file) is written at the end
982 (nothing is returned).
983
984 tmp_location requires tmpdir (which is used as gisdbase)
985 """
986 # TODO: arg param seems to be always the mapset parameter (or a dash
987 # in a distant past), refactor
988 l = arg
989 if l:
990 if l == '.':
991 l = os.getcwd()
992 elif not os.path.isabs(l):
993 l = os.path.abspath(l)
994
995 l, mapset = os.path.split(l)
996 if not mapset:
997 l, mapset = os.path.split(l)
998 l, location_name = os.path.split(l)
999 gisdbase = l
1000
1001 # all was None for tmp loc so that case goes here quickly
1002 # TODO: but the above code needs review anyway
1003 if tmp_location:
1004 # set gisdbase to temporary directory
1005 gisdbase = tmpdir
1006 # we are already in a unique directory, so we can use fixed name
1007 location_name = "tmploc"
1008 # we need only one mapset
1009 mapset = "PERMANENT"
1010 debug("Using temporary location <{gdb}{sep}{lc}>".format(
1011 gdb=gisdbase, lc=location_name, sep=os.path.sep))
1012
1013 if gisdbase and location_name and mapset:
1014 path = os.path.join(gisdbase, location_name, mapset)
1015 # check if 'path' is a valid GRASS location/mapset
1016 path_is_valid_mapset = is_mapset_valid(path)
1017
1018 if path_is_valid_mapset and create_new:
1019 warning(_("Mapset <{}> already exists. Ignoring the"
1020 " request to create it. Note that this warning"
1021 " may become an error in future versions.")
1022 .format(path))
1023
1024 if not path_is_valid_mapset:
1025 if not create_new:
1026 # 'path' is not a valid mapset and user does not
1027 # want to create anything new
1028 fatal(get_mapset_invalid_reason(gisdbase, location_name, mapset))
1029 else:
1030 # 'path' is not valid and the user wants to create
1031 # mapset on the fly
1032 # check if 'location_name' is a valid GRASS location
1033 if not is_location_valid(gisdbase, location_name):
1034 if not tmp_location:
1035 # 'location_name' is not a valid GRASS location
1036 # and user requested its creation, so we parsed
1037 # the path wrong and need to move one level
1038 # and use 'PERMANENT' mapset
1039 # (we already got that right in case of tmploc)
1040 gisdbase = os.path.join(gisdbase, location_name)
1041 location_name = mapset
1042 mapset = "PERMANENT"
1043 if not can_create_location(gisdbase, location_name):
1044 fatal(cannot_create_location_reason(
1045 gisdbase, location_name))
1046 # create new location based on the provided EPSG/...
1047 message(_("Creating new GRASS GIS location <{}>...")
1048 .format(location_name))
1049 create_location(gisdbase, location_name, geofile)
1050 else:
1051 # 'location_name' is a valid GRASS location,
1052 # create new mapset
1053 message(_("Creating new GRASS GIS mapset <{}>...")
1054 .format(mapset))
1055 if os.path.isfile(path):
1056 # not a valid mapset, but dir exists, assuming
1057 # broken/incomplete mapset
1058 fatal(_("Unable to create new mapset <{mapset}>"
1059 " because <{path}> is a file.")
1060 .format(mapset=mapset, path=path))
1061 elif os.path.isdir(path):
1062 # not a valid mapset, but dir exists, assuming
1063 # broken/incomplete mapset
1064 warning(_("The mapset <{}> is missing the WIND file"
1065 " (computational region). It will be"
1066 " fixed now. Note that this warning"
1067 " may become an error in future versions.")
1068 .format(mapset))
1069 else:
1070 # create mapset directory
1071 os.mkdir(path)
1072 # make directory a mapset, add the region
1073 # copy PERMANENT/DEFAULT_WIND to <mapset>/WIND
1074 s = readfile(os.path.join(gisdbase, location_name,
1075 "PERMANENT", "DEFAULT_WIND"))
1076 writefile(os.path.join(path, "WIND"), s)
1077
1078 if os.access(gisrc, os.R_OK):
1079 kv = read_gisrc(gisrc)
1080 else:
1081 kv = {}
1082
1083 kv['GISDBASE'] = gisdbase
1084 kv['LOCATION_NAME'] = location_name
1085 kv['MAPSET'] = mapset
1086 write_gisrc(kv, gisrc)
1087 else:
1088 fatal(_("GRASS GIS database directory, location and mapset"
1089 " not set properly."
1090 " Use GUI or command line to set them."))
1091
1092
1093def set_mapset_interactive(grass_gui):
1094 """User selects Location and Mapset in an interative way
1095
1096 The gisrc (GRASS environment file) is written at the end.
1097 """
1098 if not os.path.exists(wxpath("gis_set.py")) and grass_gui != 'text':
1099 debug("No GUI available, switching to text mode")
1100 return False
1101
1102 # Check for text interface
1103 if grass_gui == 'text':
1104 # TODO: maybe this should be removed and solved from outside
1105 # this depends on what we expect from this function
1106 # should gisrc be ok after running or is it allowed to be still not set
1107 pass
1108 # Check for GUI
1109 elif grass_gui in ('gtext', 'wxpython'):
1110 gui_startup(grass_gui)
1111 else:
1112 # Shouldn't need this but you never know
1113 fatal(_("Invalid user interface specified - <%s>. "
1114 "Use the --help option to see valid interface names.") % grass_gui)
1115
1116 return True
1117
1118
1119def gui_startup(grass_gui):
1120 """Start GUI for startup (setting gisrc file)"""
1121 if grass_gui in ('wxpython', 'gtext'):
1122 ret = call([os.getenv('GRASS_PYTHON'), wxpath("gis_set.py")])
1123
1124 # this if could be simplified to three branches (0, 5, rest)
1125 # if there is no need to handle unknown code separately
1126 if ret == 0:
1127 pass
1128 elif ret in [1, 2]:
1129 # 1 probably error coming from gis_set.py
1130 # 2 probably file not found from python interpreter
1131 # formerly we were starting in text mode instead, now we just fail
1132 # which is more straightforward for everybody
1133 fatal(_("Error in GUI startup. See messages above (if any)"
1134 " and if necessary, please"
1135 " report this error to the GRASS developers.\n"
1136 "On systems with package manager, make sure you have the right"
1137 " GUI package, probably named grass-gui, installed.\n"
1138 "To run GRASS GIS in text mode use the --text flag.\n"
1139 "Use '--help' for further options\n"
1140 " {cmd_name} --help\n"
1141 "See also: https://grass.osgeo.org/{cmd_name}/manuals/helptext.html").format(
1142 cmd_name=cmd_name))
1143 elif ret == 5: # defined in gui/wxpython/gis_set.py
1144 # User wants to exit from GRASS
1145 message(_("Exit was requested in GUI.\nGRASS GIS will not start. Bye."))
1146 sys.exit(0)
1147 else:
1148 fatal(_("Invalid return code from GUI startup script.\n"
1149 "Please advise GRASS developers of this error."))
1150
1151
1152# we don't follow the LOCATION_NAME legacy naming here but we have to still
1153# translate to it, so always double check
1154class MapsetSettings(object):
1155 """Holds GRASS GIS database directory, Location and Mapset
1156
1157 Provides few convenient functions.
1158 """
1159 def __init__(self):
1160 self.gisdbase = None
1161 self.location = None
1162 self.mapset = None
1163 self._full_mapset = None
1164
1165 # TODO: perhaps full_mapset would be better as mapset_path
1166 # TODO: works only when set for the first time
1167 # this follows the current usage but we must invalidate when
1168 # the others are changed (use properties for that)
1169 @property
1170 def full_mapset(self):
1171 if self._full_mapset is None:
1172 self._full_mapset = os.path.join(self.gisdbase, self.location,
1173 self.mapset)
1174 return self._full_mapset
1175
1176 # TODO: perhaps conversion to bool would be nicer
1177 def is_valid(self):
1178 return self.gisdbase and self.location and self.mapset
1179
1180
1181# TODO: does it really makes sense to tell user about gisrcrc?
1182# anything could have happened in between loading from gisrcrc and now
1183# (we do e.g. GUI or creating loctation)
1184def load_gisrc(gisrc, gisrcrc):
1185 """Get the settings of Location and Mapset from the gisrc file
1186
1187 :returns: MapsetSettings object
1188 """
1189 mapset_settings = MapsetSettings()
1190 kv = read_gisrc(gisrc)
1191 mapset_settings.gisdbase = kv.get('GISDBASE')
1192 mapset_settings.location = kv.get('LOCATION_NAME')
1193 mapset_settings.mapset = kv.get('MAPSET')
1194 if not mapset_settings.is_valid():
1195 fatal(_("Error reading data path information from g.gisenv.\n"
1196 "GISDBASE={gisbase}\n"
1197 "LOCATION_NAME={location}\n"
1198 "MAPSET={mapset}\n\n"
1199 "Check the <{file}> file.").format(
1200 gisbase=mapset_settings.gisdbase,
1201 location=mapset_settings.location,
1202 mapset=mapset_settings.mapset,
1203 file=gisrcrc))
1204 return mapset_settings
1205
1206
1207# load environmental variables from grass_env_file
1208def load_env(grass_env_file):
1209 if not os.access(grass_env_file, os.R_OK):
1210 return
1211
1212 for line in readfile(grass_env_file).split(os.linesep):
1213 try:
1214 k, v = map(lambda x: x.strip(), line.strip().split(' ', 1)[1].split('=', 1))
1215 except:
1216 continue
1217
1218 debug("Environmental variable set {0}={1}".format(k, v))
1219 os.environ[k] = v
1220
1221 # Allow for mixed ISIS-GRASS Environment
1222 if os.getenv('ISISROOT'):
1223 isis = os.getenv('ISISROOT')
1224 os.environ['ISIS_LIB'] = isis + os.sep + "lib"
1225 os.environ['ISIS_3RDPARTY'] = isis + os.sep + "3rdParty" + os.sep + "lib"
1226 os.environ['QT_PLUGIN_PATH'] = isis + os.sep + "3rdParty" + os.sep + "plugins"
1227 # os.environ['ISIS3DATA'] = isis + "$ISIS3DATA"
1228 libpath = os.getenv('LD_LIBRARY_PATH', '')
1229 isislibpath = os.getenv('ISIS_LIB')
1230 isis3rdparty = os.getenv('ISIS_3RDPARTY')
1231 os.environ['LD_LIBRARY_PATH'] = libpath + os.pathsep + isislibpath + os.pathsep + isis3rdparty
1232
1233
1234def set_language(grass_config_dir):
1235 # This function is used to override system default language and locale
1236 # Such override can be requested only from wxGUI
1237 # An override if user has provided correct environmental variables as
1238 # LC_MESSAGES or LANG is not necessary.
1239 # Unfortunately currently a working solution for Windows is lacking
1240 # thus it always on Vista and XP will print an error.
1241 # See discussion for Windows not following its own documentation and
1242 # not accepting ISO codes as valid locale identifiers http://bugs.python.org/issue10466
1243 language = 'None' # Such string sometimes is present in wx file
1244 encoding = None
1245
1246 # Override value is stored in wxGUI preferences file.
1247 # As it's the only thing required, we'll just grep it out.
1248 try:
1249 fd = open(os.path.join(grass_config_dir, 'wx'), 'r')
1250 except:
1251 # Even if there is no override, we still need to set locale.
1252 pass
1253 else:
1254 for line in fd:
1255 if re.search('^language', line):
1256 line = line.rstrip(' %s' % os.linesep)
1257 language = ''.join(line.split(';')[-1:])
1258 break
1259 fd.close()
1260
1261 # Backwards compatibility with old wx preferences files
1262 if language == 'C':
1263 language = 'en'
1264
1265 if language == 'None' or language == '' or not language:
1266 # Language override is disabled (system language specified)
1267 # As by default program runs with C locale, but users expect to
1268 # have their default locale, we'll just set default locale
1269 try:
1270 locale.setlocale(locale.LC_ALL, '')
1271 except locale.Error as e:
1272 # If we get here, system locale settings are terribly wrong
1273 # There is no point to continue as GRASS/Python will fail
1274 # in some other unpredictable way.
1275 sys.stderr.write(
1276 "System locale is not usable (LC_ALL variable not defined)."
1277 " Most likely it indicates misconfigured environment.\n")
1278 sys.stderr.write("Reported error message: %s\n" % e)
1279 # it would be too drastic to exit
1280 # sys.exit("Fix system locale settings and then try again.")
1281 locale.setlocale(locale.LC_ALL, 'C')
1282 sys.stderr.write("Default locale settings are missing. GRASS running with C locale.")
1283
1284 language, encoding = locale.getdefaultlocale()
1285 if not language:
1286 sys.stderr.write("Default locale settings are missing. GRASS running with C locale.")
1287 return
1288
1289 else:
1290 debug("A language override has been requested."
1291 " Trying to switch GRASS into '%s'..." % language)
1292 try:
1293 locale.setlocale(locale.LC_ALL, language)
1294 except locale.Error as e:
1295 try:
1296 # Locale lang.encoding might be missing. Let's try
1297 # UTF-8 encoding before giving up as on Linux systems
1298 # lang.UTF-8 locales are more common than legacy
1299 # ISO-8859 ones.
1300 encoding = 'UTF-8'
1301 normalized = locale.normalize('%s.%s' % (language, encoding))
1302 locale.setlocale(locale.LC_ALL, normalized)
1303 except locale.Error as e:
1304 if language == 'en':
1305 # A workaround for Python Issue30755
1306 # https://bugs.python.org/issue30755
1307 if locale.normalize('C.UTF-8') == 'en_US.UTF-8':
1308 locale.setlocale(locale.LC_ALL, 'C')
1309 os.environ['LANGUAGE'] = 'C'
1310 os.environ['LANG'] = 'C'
1311 os.environ['LC_MESSAGES'] = 'C'
1312 os.environ['LC_NUMERIC'] = 'C'
1313 os.environ['LC_TIME'] = 'C'
1314 sys.stderr.write(
1315 "To avoid Unicode errors in GUI, install"
1316 " en_US.UTF-8 locale and restart GRASS.\n"
1317 "Also consider upgrading your Python version"
1318 " to one containing fix for Python Issue 30755.\n")
1319 return
1320 # en_US locale might be missing, still all messages in
1321 # GRASS are already in en_US language.
1322 # Using plain C as locale forces encodings to ascii
1323 # thus lets try our luck with C.UTF-8 first.
1324 # See bugs #3441 and #3423
1325 try:
1326 locale.setlocale(locale.LC_ALL, 'C.UTF-8')
1327 except locale.Error as e:
1328 # All lost. Setting to C as much as possible.
1329 # We can not call locale.normalize on C as it
1330 # will transform it to en_US and we already know
1331 # it doesn't work.
1332 locale.setlocale(locale.LC_ALL, 'C')
1333 os.environ['LANGUAGE'] = 'C'
1334 os.environ['LANG'] = 'C'
1335 os.environ['LC_MESSAGES'] = 'C'
1336 os.environ['LC_NUMERIC'] = 'C'
1337 os.environ['LC_TIME'] = 'C'
1338 gettext.install('grasslibs', gpath('locale'))
1339 sys.stderr.write(
1340 "All attempts to enable English language have"
1341 " failed. GRASS running with C locale.\n"
1342 "If you observe UnicodeError in Python,"
1343 " install en_US.UTF-8"
1344 " locale and restart GRASS.\n")
1345 return
1346 else:
1347 # The last attempt...
1348 try:
1349 encoding = locale.getpreferredencoding()
1350 normalized = locale.normalize('%s.%s' % (language, encoding))
1351 locale.setlocale(locale.LC_ALL, normalized)
1352 except locale.Error as e:
1353 # If we got so far, attempts to set up language and locale have failed
1354 # on this system
1355 sys.stderr.write("Failed to enforce user specified language '%s' with error: '%s'\n" % (language, e))
1356 sys.stderr.write("A LANGUAGE environmental variable has been set.\nPart of messages will be displayed in the requested language.\n")
1357 # Even if setting locale will fail, let's set LANG in a hope,
1358 # that UI will use it GRASS texts will be in selected language,
1359 # system messages (i.e. OK, Cancel etc.) - in system default
1360 # language
1361 os.environ['LANGUAGE'] = language
1362 os.environ['LANG'] = language
1363 return
1364
1365 # Set up environment for subprocesses
1366 os.environ['LANGUAGE'] = language
1367 os.environ['LANG'] = language
1368
1369 if language == 'ko_KR' and encoding == 'cp949':
1370 # The default encoding for the Korean language in Windows is cp949,
1371 # Microsoft's proprietary extension to euc-kr, but gettext prints no
1372 # translated messages at all in the Command Prompt window if LC_CTYPE
1373 # is set to ko_KR.cp949. Here, force LC_CTYPE to be euc-kr.
1374 normalized = 'euc-kr'
1375 encoding = None
1376 elif encoding:
1377 normalized = locale.normalize('%s.%s' % (language, encoding))
1378 else:
1379 normalized = language
1380
1381 for lc in ('LC_CTYPE', 'LC_MESSAGES', 'LC_TIME', 'LC_COLLATE',
1382 'LC_MONETARY', 'LC_PAPER', 'LC_NAME', 'LC_ADDRESS',
1383 'LC_TELEPHONE', 'LC_MEASUREMENT', 'LC_IDENTIFICATION'):
1384 os.environ[lc] = normalized
1385
1386 # Some code in GRASS might not like other decimal separators than .
1387 # Other potential sources for problems are: LC_TIME LC_CTYPE
1388 locale.setlocale(locale.LC_NUMERIC, 'C')
1389 os.environ['LC_NUMERIC'] = 'C'
1390 if os.getenv('LC_ALL'):
1391 del os.environ['LC_ALL'] # Remove LC_ALL to not override LC_NUMERIC
1392
1393 # From now on enforce the new language
1394 if encoding:
1395 gettext.install('grasslibs', gpath('locale'), codeset=encoding)
1396 else:
1397 gettext.install('grasslibs', gpath('locale'))
1398
1399
1400# TODO: grass_gui parameter is a hack and should be removed, see below
1401def lock_mapset(mapset_path, force_gislock_removal, user, grass_gui):
1402 """Lock the mapset and return name of the lock file
1403
1404 Behavior on error must be changed somehow; now it fatals but GUI case is
1405 unresolved.
1406 """
1407 if not os.path.exists(mapset_path):
1408 fatal(_("Path '%s' doesn't exist") % mapset_path)
1409 if not os.access(mapset_path, os.W_OK):
1410 error = _("Path '%s' not accessible.") % mapset_path
1411 stat_info = os.stat(mapset_path)
1412 mapset_uid = stat_info.st_uid
1413 if mapset_uid != os.getuid():
1414 # GTC %s is mapset's folder path
1415 error = "%s\n%s" % (error, _("You are not the owner of '%s'.") % mapset_path)
1416 fatal(error)
1417 # Check for concurrent use
1418 lockfile = os.path.join(mapset_path, ".gislock")
1419 ret = call([gpath("etc", "lock"), lockfile, "%d" % os.getpid()])
1420 msg = None
1421 if ret == 2:
1422 if not force_gislock_removal:
1423 msg = _("%(user)s is currently running GRASS in selected mapset"
1424 " (file %(file)s found). Concurrent use not allowed.\n"
1425 "You can force launching GRASS using -f flag"
1426 " (note that you need permission for this operation)."
1427 " Have another look in the processor "
1428 "manager just to be sure..." % {
1429 'user': user, 'file': lockfile})
1430 else:
1431 try_remove(lockfile)
1432 message(_("%(user)s is currently running GRASS in selected mapset"
1433 " (file %(file)s found). Forcing to launch GRASS..." % {
1434 'user': user, 'file': lockfile}))
1435 elif ret != 0:
1436 msg = _("Unable to properly access '%s'.\n"
1437 "Please notify system personnel.") % lockfile
1438
1439 # TODO: the gui decision should be done by the caller
1440 # this needs some change to the function interface, return tuple or
1441 # use exceptions (better option)
1442 if msg:
1443 if grass_gui == "wxpython":
1444 call([os.getenv('GRASS_PYTHON'), wxpath("gis_set_error.py"), msg])
1445 # TODO: here we probably miss fatal or exit, needs to be added
1446 else:
1447 fatal(msg)
1448 debug("Mapset <{mapset}> locked using '{lockfile}'".format(
1449 mapset=mapset_path, lockfile=lockfile))
1450 return lockfile
1451
1452
1453# TODO: the gisrcrc here does not make sense, remove it from load_gisrc
1454def unlock_gisrc_mapset(gisrc, gisrcrc):
1455 """Unlock mapset from the gisrc file"""
1456 settings = load_gisrc(gisrc, gisrcrc)
1457 lockfile = os.path.join(settings.full_mapset, ".gislock")
1458 # this fails silently, perhaps a warning would be helpful to
1459 # catch cases when removal was not possible due to e.g. another
1460 # session force-removing the file (unlocking the mapset)
1461 try_remove(lockfile)
1462
1463
1464def make_fontcap():
1465 # TODO: is GRASS_FONT_CAP ever defined? It seems it must be defined in system
1466 fc = os.getenv('GRASS_FONT_CAP')
1467 if fc and not os.access(fc, os.R_OK):
1468 message(_("Building user fontcap..."))
1469 call(["g.mkfontcap"])
1470
1471
1472def ensure_db_connected(mapset):
1473 """Predefine default driver if DB connection not defined
1474
1475 :param mapset: full path to the mapset
1476 """
1477 if not os.access(os.path.join(mapset, "VAR"), os.F_OK):
1478 call(['db.connect', '-c', '--quiet'])
1479
1480
1481def get_shell():
1482 # set SHELL on ms windowns
1483 # this was at the very beginning of the script but it can be anywhere
1484 if windows:
1485 if os.getenv('GRASS_SH'):
1486 os.environ['SHELL'] = os.getenv('GRASS_SH')
1487 if not os.getenv('SHELL'):
1488 os.environ['SHELL'] = os.getenv('COMSPEC', 'cmd.exe')
1489
1490 # cygwin has many problems with the shell setup
1491 # below, so i hardcoded everything here.
1492 if sys.platform == 'cygwin':
1493 sh = "cygwin"
1494 shellname = "GNU Bash (Cygwin)"
1495 os.environ['SHELL'] = "/usr/bin/bash.exe"
1496 os.environ['OSTYPE'] = "cygwin"
1497 else:
1498 # in docker the 'SHELL' variable may not be
1499 # visible in a Python session unless 'ENV SHELL /bin/bash' is set in Dockerfile
1500 try:
1501 sh = os.path.basename(os.getenv('SHELL'))
1502 except:
1503 sh = 'sh'
1504 os.environ['SHELL'] = sh
1505
1506 if windows and sh:
1507 sh = os.path.splitext(sh)[0]
1508
1509 if sh == "ksh":
1510 shellname = "Korn Shell"
1511 elif sh == "csh":
1512 shellname = "C Shell"
1513 elif sh == "tcsh":
1514 shellname = "TC Shell"
1515 elif sh == "bash":
1516 shellname = "Bash Shell"
1517 elif sh == "sh":
1518 shellname = "Bourne Shell"
1519 elif sh == "zsh":
1520 shellname = "Z Shell"
1521 elif sh == "cmd":
1522 shellname = "Command Prompt"
1523 elif sh == "powershell":
1524 shellname = "Windows PowerShell"
1525 else:
1526 shellname = "shell"
1527 # check for SHELL
1528 if not os.getenv('SHELL'):
1529 fatal(_("The SHELL variable is not set"))
1530 return sh, shellname
1531
1532
1533def get_grass_env_file(sh, grass_config_dir):
1534 """Get name of the shell-specific GRASS environment (rc) file"""
1535 if sh in ['csh', 'tcsh']:
1536 grass_env_file = os.path.join(grass_config_dir, 'cshrc')
1537 elif sh in ['bash', 'msh', 'cygwin', 'sh']:
1538 grass_env_file = os.path.join(grass_config_dir, 'bashrc')
1539 elif sh == 'zsh':
1540 grass_env_file = os.path.join(grass_config_dir, 'zshrc')
1541 elif sh in ['cmd', 'powershell']:
1542 grass_env_file = os.path.join(grass_config_dir, 'env.bat')
1543 else:
1544 grass_env_file = os.path.join(grass_config_dir, 'bashrc')
1545 warning(_("Unsupported shell <{sh}>: {env_file}").format(
1546 sh=sh, env_file=grass_env_file))
1547 return grass_env_file
1548
1549
1550def get_batch_job_from_env_variable():
1551 """Get script to execute from batch job variable if available
1552
1553 Fails with fatal if variable is set but content unusable.
1554 """
1555 # hack to process batch jobs:
1556 batch_job = os.getenv('GRASS_BATCH_JOB')
1557 # variable defined, but user might not have been careful enough
1558 if batch_job:
1559 if not os.access(batch_job, os.F_OK):
1560 # wrong file
1561 fatal(_("Job file <%s> has been defined in "
1562 "the 'GRASS_BATCH_JOB' variable but not found. Exiting."
1563 "\n\n"
1564 "Use 'unset GRASS_BATCH_JOB' to disable "
1565 "batch job processing.") % batch_job)
1566 elif not os.access(batch_job, os.X_OK):
1567 # right file, but ...
1568 fatal(_("Change file permission to 'executable' for <%s>")
1569 % batch_job)
1570 return batch_job
1571
1572
1573def run_batch_job(batch_job):
1574 """Runs script, module or any command
1575
1576 If *batch_job* is a string (insecure) shell=True is used for execution.
1577
1578 :param batch_job: executable and parameters in a list or a string
1579 """
1580 batch_job_string = batch_job
1581 if not isinstance(batch_job, string_types):
1582 # for messages only
1583 batch_job_string = ' '.join(batch_job)
1584 message(_("Executing <%s> ...") % batch_job_string)
1585 if isinstance(batch_job, string_types):
1586 # shell=True is keeping the original GRASS_BATCH_JOB behavior
1587 def quote(string):
1588 if '"' in string:
1589 return "'%s'" % batch_job
1590 else:
1591 return '"%s"' % batch_job
1592 batch_job = quote(batch_job)
1593 proc = Popen(batch_job, shell=True)
1594 else:
1595 try:
1596 proc = Popen(batch_job, shell=False)
1597 except OSError as error:
1598 fatal(_("Execution of <{cmd}> failed:\n"
1599 "{error}").format(cmd=batch_job_string, error=error))
1600 returncode = proc.wait()
1601 message(_("Execution of <%s> finished.") % batch_job_string)
1602 return returncode
1603
1604
1605def start_gui(grass_gui):
1606 """Start specified GUI
1607
1608 :param grass_gui: GUI name (allowed values: 'wxpython')
1609 """
1610 # Start the chosen GUI but ignore text
1611 debug("GRASS GUI should be <%s>" % grass_gui)
1612 # Check for gui interface
1613 if grass_gui == "wxpython":
1614 Popen([os.getenv('GRASS_PYTHON'), wxpath("wxgui.py")])
1615
1616
1617def close_gui():
1618 """Close GUI if running"""
1619 if gpath('etc', 'python') not in sys.path:
1620 sys.path.append(gpath('etc', 'python'))
1621 from grass.script import core as gcore # pylint: disable=E0611
1622 env = gcore.gisenv()
1623 if 'GUI_PID' not in env:
1624 return
1625 import signal
1626 for pid in env['GUI_PID'].split(','):
1627 debug("Exiting GUI with pid={0}".format(pid))
1628 try:
1629 os.kill(int(pid), signal.SIGTERM)
1630 except OSError as e:
1631 message(_("Unable to close GUI. {0}").format(e))
1632
1633
1634def clear_screen():
1635 """Clear terminal"""
1636 if windows:
1637 pass
1638 # TODO: uncomment when PDCurses works.
1639 # cls
1640 else:
1641 if not is_debug():
1642 call(["tput", "clear"])
1643
1644
1645def show_banner():
1646 """Write GRASS GIS ASCII name to stderr"""
1647 sys.stderr.write(r"""
1648 __________ ___ __________ _______________
1649 / ____/ __ \/ | / ___/ ___/ / ____/ _/ ___/
1650 / / __/ /_/ / /| | \__ \\_ \ / / __ / / \__ \
1651 / /_/ / _, _/ ___ |___/ /__/ / / /_/ // / ___/ /
1652 \____/_/ |_/_/ |_/____/____/ \____/___//____/
1653
1654""")
1655
1656
1657def say_hello():
1658 """Write welcome to stderr including Subversion revision if in svn copy"""
1659 sys.stderr.write(_("Welcome to GRASS GIS %s") % grass_version)
1660 if grass_version.endswith('svn'):
1661 try:
1662 filerev = open(gpath('etc', 'VERSIONNUMBER'))
1663 linerev = filerev.readline().rstrip('\n')
1664 filerev.close()
1665
1666 revision = linerev.split(' ')[1]
1667 sys.stderr.write(' (' + revision + ')')
1668 except:
1669 pass
1670
1671
1672def show_info(shellname, grass_gui, default_gui):
1673 """Write basic info about GRASS GIS and GRASS session to stderr"""
1674 sys.stderr.write(
1675r"""
1676%-41shttps://grass.osgeo.org
1677%-41s%s (%s)
1678%-41sg.manual -i
1679%-41sg.version -c
1680%-41sg.version -x
1681""" % (_("GRASS GIS homepage:"),
1682 # GTC Running through: SHELL NAME
1683 _("This version running through:"),
1684 shellname, os.getenv('SHELL'),
1685 _("Help is available with the command:"),
1686 _("See the licence terms with:"),
1687 _("See citation options with:")))
1688
1689 if grass_gui == 'wxpython':
1690 message("%-41sg.gui wxpython" % _("If required, restart the GUI with:"))
1691 else:
1692 message("%-41sg.gui %s" % (_("Start the GUI with:"), default_gui))
1693
1694 message("%-41sexit" % _("When ready to quit enter:"))
1695 message("")
1696
1697
1698def csh_startup(location, location_name, mapset, grass_env_file):
1699 userhome = os.getenv('HOME') # save original home
1700 home = location
1701 os.environ['HOME'] = home
1702
1703 cshrc = os.path.join(home, ".cshrc")
1704 tcshrc = os.path.join(home, ".tcshrc")
1705 try_remove(cshrc)
1706 try_remove(tcshrc)
1707
1708 f = open(cshrc, 'w')
1709 f.write("set home = %s\n" % userhome)
1710 f.write("set history = 3000 savehist = 3000 noclobber ignoreeof\n")
1711 f.write("set histfile = %s\n" % os.path.join(os.getenv('HOME'),
1712 ".history"))
1713
1714 f.write("set prompt = '\\\n")
1715 f.write("Mapset <%s> in Location <%s> \\\n" % (mapset, location_name))
1716 f.write("GRASS GIS %s > '\n" % grass_version)
1717 f.write("set BOGUS=``;unset BOGUS\n")
1718
1719 # csh shell rc file left for backward compatibility
1720 path = os.path.join(userhome, ".grass.cshrc")
1721 if os.access(path, os.R_OK):
1722 f.write(readfile(path) + '\n')
1723 if os.access(grass_env_file, os.R_OK):
1724 f.write(readfile(grass_env_file) + '\n')
1725
1726 mail_re = re.compile(r"^ *set *mail *= *")
1727
1728 for filename in [".cshrc", ".tcshrc", ".login"]:
1729 path = os.path.join(userhome, filename)
1730 if os.access(path, os.R_OK):
1731 s = readfile(path)
1732 lines = s.splitlines()
1733 for l in lines:
1734 if mail_re.match(l):
1735 f.write(l)
1736
1737 path = os.getenv('PATH').split(':')
1738 f.write("set path = ( %s ) \n" % ' '.join(path))
1739 f.close()
1740 writefile(tcshrc, readfile(cshrc))
1741
1742 process = Popen([gpath("etc", "run"), os.getenv('SHELL')])
1743 os.environ['HOME'] = userhome
1744 return process
1745
1746
1747def bash_startup(location, location_name, grass_env_file):
1748 # save command history in mapset dir and remember more
1749 os.environ['HISTFILE'] = os.path.join(location, ".bash_history")
1750 if not os.getenv('HISTSIZE') and not os.getenv('HISTFILESIZE'):
1751 os.environ['HISTSIZE'] = "3000"
1752
1753 # instead of changing $HOME, start bash with:
1754 # --rcfile "$LOCATION/.bashrc" ?
1755 # if so, must care be taken to explicitly call .grass.bashrc et al
1756 # for non-interactive bash batch jobs?
1757 userhome = os.getenv('HOME') # save original home
1758 home = location # save .bashrc in $LOCATION
1759 os.environ['HOME'] = home
1760
1761 bashrc = os.path.join(home, ".bashrc")
1762 try_remove(bashrc)
1763
1764 f = open(bashrc, 'w')
1765 f.write("test -r ~/.alias && . ~/.alias\n")
1766
1767 if os.getenv('ISISROOT'):
1768 # GRASS GIS and ISIS blend
1769 grass_name = "ISIS-GRASS"
1770 else:
1771 grass_name = "GRASS"
1772 f.write("PS1='{name} {version} ({location}):\\w > '\n".format(
1773 name=grass_name, version=grass_version, location=location_name))
1774
1775 # TODO: have a function and/or module to test this
1776 mask2d_test = 'test -f "$MAPSET_PATH/cell/MASK"'
1777 mask3d_test = 'test -d "$MAPSET_PATH/grid3/RASTER3D_MASK"'
1778
1779 # double curly brackets means single one for format function
1780 # setting LOCATION for backwards compatibility
1781 f.write(
1782 """grass_prompt() {{
1783 MAPSET_PATH="`g.gisenv get=GISDBASE,LOCATION_NAME,MAPSET separator='/'`"
1784 LOCATION="$MAPSET_PATH"
1785 if {mask2d_test} && {mask3d_test} ; then
1786 echo [{both_masks}]
1787 elif {mask2d_test} ; then
1788 echo [{mask2d}]
1789 elif {mask3d_test} ; then
1790 echo [{mask3d}]
1791 fi
1792}}
1793PROMPT_COMMAND=grass_prompt\n""".format(
1794 both_masks=_("2D and 3D raster MASKs present"),
1795 mask2d=_("Raster MASK present"),
1796 mask3d=_("3D raster MASK present"),
1797 mask2d_test=mask2d_test, mask3d_test=mask3d_test
1798 ))
1799
1800 # read other settings (aliases, ...) since environmental variables
1801 # have been already set by load_env(), see #3462
1802 for env_file in [os.path.join(userhome, ".grass.bashrc"),
1803 grass_env_file]:
1804 if not os.access(env_file, os.R_OK):
1805 continue
1806 for line in readfile(env_file).splitlines():
1807 # Bug related to OS X "SIP", see
1808 # https://trac.osgeo.org/grass/ticket/3462#comment:13
1809 if macosx or not line.startswith('export'):
1810 f.write(line + '\n')
1811
1812 f.write("export PATH=\"%s\"\n" % os.getenv('PATH'))
1813 f.write("export HOME=\"%s\"\n" % userhome) # restore user home path
1814 f.close()
1815
1816 process = Popen([gpath("etc", "run"), os.getenv('SHELL')])
1817 os.environ['HOME'] = userhome
1818 return process
1819
1820
1821def default_startup(location, location_name):
1822 if windows:
1823 os.environ['PS1'] = "GRASS %s> " % (grass_version)
1824 # "$ETC/run" doesn't work at all???
1825 process = subprocess.Popen([os.getenv('SHELL')])
1826 else:
1827 os.environ['PS1'] = "GRASS %s (%s):\\w > " % (grass_version, location_name)
1828 process = Popen([gpath("etc", "run"), os.getenv('SHELL')])
1829
1830 return process
1831
1832
1833def done_message():
1834 # here was something for batch job but it was never called
1835 message(_("Done."))
1836 message("")
1837 message(_("Goodbye from GRASS GIS"))
1838 message("")
1839
1840
1841def clean_temp():
1842 message(_("Cleaning up temporary files..."))
1843 nul = open(os.devnull, 'w')
1844 call([gpath("etc", "clean_temp")], stdout=nul)
1845 nul.close()
1846
1847
1848def clean_all():
1849 from grass.script import setup as gsetup
1850 # clean default sqlite db
1851 gsetup.clean_default_db()
1852 # remove leftover temp files
1853 clean_temp()
1854 # save 'last used' GISRC after removing variables which shouldn't
1855 # be saved, e.g. d.mon related
1856 clean_env(os.environ['GISRC'])
1857
1858
1859def grep(pattern, lines):
1860 """Search lines (list of strings) and return them when beginning matches.
1861
1862 >>> grep("a", ['abc', 'cab', 'sdr', 'aaa', 'sss'])
1863 ['abc', 'aaa']
1864 """
1865 expr = re.compile(pattern)
1866 return [elem for elem in lines if expr.match(elem)]
1867
1868
1869def print_params():
1870 """Write compile flags and other configuration to stderr"""
1871 plat = gpath('include', 'Make', 'Platform.make')
1872 if not os.path.exists(plat):
1873 fatal(_("Please install the GRASS GIS development package"))
1874 fileplat = open(plat)
1875 linesplat = fileplat.readlines()
1876 fileplat.close()
1877
1878 params = sys.argv[2:]
1879 if not params:
1880 params = ['arch', 'build', 'compiler', 'path', 'revision', 'version']
1881
1882 for arg in params:
1883 if arg == 'path':
1884 sys.stdout.write("%s\n" % gisbase)
1885 elif arg == 'arch':
1886 val = grep('ARCH', linesplat)
1887 sys.stdout.write("%s\n" % val[0].split('=')[1].strip())
1888 elif arg == 'build':
1889 build = gpath('include', 'grass', 'confparms.h')
1890 filebuild = open(build)
1891 val = filebuild.readline()
1892 filebuild.close()
1893 sys.stdout.write("%s\n" % val.strip().strip('"').strip())
1894 elif arg == 'compiler':
1895 val = grep('CC', linesplat)
1896 sys.stdout.write("%s\n" % val[0].split('=')[1].strip())
1897 elif arg == 'revision':
1898 rev = gpath('include', 'grass', 'gis.h')
1899 filerev = open(rev)
1900 linesrev = filerev.readlines()
1901 val = grep('#define GIS_H_VERSION', linesrev)
1902 filerev.close()
1903 sys.stdout.write(
1904 "%s\n" % val[0].split(':')[1].rstrip('$"\n').strip())
1905 elif arg == 'svn_revision':
1906 filerev = open(gpath('etc', 'VERSIONNUMBER'))
1907 linerev = filerev.readline().rstrip('\n')
1908 filerev.close()
1909 try:
1910 revision = linerev.split(' ')[1]
1911 sys.stdout.write("%s\n" % revision[1:])
1912 except:
1913 sys.stdout.write("No SVN revision defined\n")
1914 elif arg == 'version':
1915 sys.stdout.write("%s\n" % grass_version)
1916 else:
1917 message(_("Parameter <%s> not supported") % arg)
1918
1919
1920def get_username():
1921 """Get name of the current user"""
1922 if windows:
1923 user = os.getenv('USERNAME')
1924 if not user:
1925 user = "user_name"
1926 else:
1927 user = os.getenv('USER')
1928 if not user:
1929 user = os.getenv('LOGNAME')
1930 if not user:
1931 try:
1932 p = Popen(['whoami'], stdout=subprocess.PIPE)
1933 s = p.stdout.read()
1934 p.wait()
1935 user = s.strip()
1936 if type(user) is bytes:
1937 user = decode(user)
1938 except:
1939 pass
1940 if not user:
1941 user = "user_%d" % os.getuid()
1942 return user
1943
1944
1945class Parameters(object):
1946 """Structure to hold standard part of command line parameters"""
1947 # we don't need to define any methods
1948 # pylint: disable=R0903
1949 def __init__(self):
1950 self.grass_gui = None
1951 self.create_new = None
1952 self.exit_grass = None
1953 self.force_gislock_removal = None
1954 self.mapset = None
1955 self.geofile = None
1956 self.tmp_location = False
1957
1958
1959def parse_cmdline(argv, default_gui):
1960 """Parse the standard part of command line parameters"""
1961 params = Parameters()
1962 args = []
1963 for i in argv:
1964 # Check if the user asked for the version
1965 if i in ["-v", "--version"]:
1966 message("GRASS GIS %s" % grass_version)
1967 message('\n' + readfile(gpath("etc", "license")))
1968 sys.exit()
1969 # Check if the user asked for help
1970 elif i in ["help", "-h", "-help", "--help", "--h"]:
1971 help_message(default_gui=default_gui)
1972 sys.exit()
1973 # Check if the --text flag was given
1974 elif i in ["-text", "--text"]:
1975 params.grass_gui = 'text'
1976 # Check if the --gtext flag was given
1977 elif i in ["-gtext", "--gtext"]:
1978 params.grass_gui = 'gtext'
1979 # Check if the --gui flag was given
1980 elif i in ["-gui", "--gui"]:
1981 params.grass_gui = default_gui
1982 # Check if the -wxpython flag was given
1983 elif i in ["-wxpython", "-wx", "--wxpython", "--wx"]:
1984 params.grass_gui = 'wxpython'
1985 # Check if the user wants to create a new mapset
1986 elif i == "-c":
1987 params.create_new = True
1988 elif i == "-e":
1989 params.exit_grass = True
1990 elif i == "-f":
1991 params.force_gislock_removal = True
1992 elif i == "--config":
1993 print_params()
1994 sys.exit()
1995 elif i == "--tmp-location":
1996 params.tmp_location = True
1997 else:
1998 args.append(i)
1999 if len(args) > 1:
2000 params.mapset = args[1]
2001 params.geofile = args[0]
2002 elif len(args) == 1:
2003 if params.tmp_location:
2004 params.geofile = args[0]
2005 else:
2006 params.mapset = args[0]
2007 else:
2008 params.mapset = None
2009 return params
2010
2011
2012# The main script starts here
2013
2014# Get the system name
2015windows = sys.platform == 'win32'
2016cygwin = "cygwin" in sys.platform
2017macosx = "darwin" in sys.platform
2018
2019# TODO: it is OK to remove this?
2020# at the beginning of this file were are happily getting GISBASE
2021# from the environment and we don't care about inconsistencies it might cause
2022# The following was commented out because of breaking winGRASS
2023# if 'GISBASE' in os.environ:
2024# sys.exit(_("ERROR: GRASS GIS is already running "
2025# "(environmental variable GISBASE found)"))
2026# this is not really an issue, we should be able to overpower another session
2027
2028# Set GISBASE
2029os.environ['GISBASE'] = gisbase
2030
2031
2032def main():
2033 """The main function which does the whole setup and run procedure
2034
2035 Only few things are set on the module level.
2036 """
2037 # Set default GUI
2038 default_gui = "wxpython"
2039
2040 # explain what is happening in debug mode (visible only in debug mode)
2041 debug("GRASS_DEBUG environmental variable is set. It is meant to be"
2042 " an internal variable for debugging only this script.\n"
2043 " Use 'g.gisenv set=\"DEBUG=[0-5]\"'"
2044 " to turn GRASS GIS debug mode on if you wish to do so.")
2045
2046 # Set GRASS version number for R interface etc
2047 # (must be an env var for MS Windows)
2048 os.environ['GRASS_VERSION'] = grass_version
2049
2050 # Set the GIS_LOCK variable to current process id
2051 gis_lock = str(os.getpid())
2052 os.environ['GIS_LOCK'] = gis_lock
2053
2054 grass_config_dir = get_grass_config_dir()
2055
2056 batch_job = get_batch_job_from_env_variable()
2057
2058 # Parse the command-line options and set several global variables
2059 batch_exec_param = '--exec'
2060 try:
2061 # raises ValueError when not found
2062 index = sys.argv.index(batch_exec_param)
2063 batch_job = sys.argv[index + 1:]
2064 clean_argv = sys.argv[1:index]
2065 params = parse_cmdline(clean_argv, default_gui=default_gui)
2066 except ValueError:
2067 params = parse_cmdline(sys.argv[1:], default_gui=default_gui)
2068 if params.exit_grass and not params.create_new:
2069 fatal(_("Flag -e requires also flag -c"))
2070 if params.tmp_location and not params.geofile:
2071 fatal(_("Coordinate reference system argument (e.g. EPSG)"
2072 " is needed for --tmp-location"))
2073 if params.tmp_location and params.mapset:
2074 fatal(_("Only one argument (e.g. EPSG) is needed for"
2075 " --tmp-location, mapset name <{}> provided").format(
2076 params.mapset))
2077 # For now, we allow, but not advertise/document, --tmp-location
2078 # without --exec (usefulness to be evaluated).
2079
2080 grass_gui = params.grass_gui # put it to variable, it is used a lot
2081
2082 # TODO: with --tmp-location there is no point in loading settings
2083 # i.e. rc file from home dir, but the code is too spread out
2084 # to disable it at this point
2085 gisrcrc = get_gisrc_from_config_dir(grass_config_dir, batch_job)
2086
2087 # Set the username
2088 user = get_username()
2089
2090 # TODO: this might need to be moved before processing of parameters
2091 # and getting batch job
2092 # Set language
2093 # This has to be called before any _() function call!
2094 # Subsequent functions are using _() calls and
2095 # thus must be called only after Language has been set.
2096 set_language(grass_config_dir)
2097
2098 # Set shell (needs to be called before load_env())
2099 sh, shellname = get_shell()
2100 grass_env_file = get_grass_env_file(sh, grass_config_dir)
2101
2102 # Load environmental variables from the file (needs to be called
2103 # before create_tmp())
2104 load_env(grass_env_file)
2105
2106 # Create the temporary directory and session grassrc file
2107 tmpdir = create_tmp(user, gis_lock)
2108
2109 cleaner = Cleaner()
2110 cleaner.tmpdir = tmpdir
2111 # object is not destroyed when its method is registered
2112 atexit.register(cleaner.cleanup)
2113
2114 # Create the session grassrc file
2115 gisrc = create_gisrc(tmpdir, gisrcrc)
2116
2117 ensure_home()
2118 # Set PATH, PYTHONPATH, ...
2119 set_paths(grass_config_dir=grass_config_dir)
2120 # Set GRASS_PAGER, GRASS_PYTHON, GRASS_GNUPLOT, GRASS_PROJSHARE
2121 set_defaults()
2122 set_display_defaults()
2123 # Set GRASS_HTML_BROWSER
2124 set_browser()
2125
2126 # First time user - GISRC is defined in the GRASS script
2127 if not os.access(gisrc, os.F_OK):
2128 if grass_gui == 'text' and not params.mapset:
2129 fatal(_("Unable to start GRASS GIS. You have the choice to:\n"
2130 " - Launch the graphical user interface with"
2131 " the '--gui' switch\n"
2132 " {cmd_name} --gui\n"
2133 " - Launch with path to "
2134 "the location/mapset as an argument\n"
2135 " {cmd_name} /path/to/location/mapset`\n"
2136 " - Create a location with '-c' and launch in its"
2137 " PERMANENT mapset\n"
2138 " {cmd_name} -c EPSG:number /path/to/location\n"
2139 " {cmd_name} -c geofile /path/to/location\n"
2140 " - Create manually the GISRC file ({gisrcrc})\n"
2141 " - Use '--help' for further options\n"
2142 " {cmd_name} --help\n"
2143 "See also: https://grass.osgeo.org/{cmd_name}/manuals/helptext.html").format(
2144 cmd_name=cmd_name, gisrcrc=gisrcrc))
2145 create_initial_gisrc(gisrc)
2146
2147 message(_("Starting GRASS GIS..."))
2148
2149 # Ensure GUI is set
2150 if batch_job or params.exit_grass:
2151 grass_gui = 'text'
2152 else:
2153 if not grass_gui:
2154 # if GUI was not set previously (e.g. command line),
2155 # get it from rc file or env variable
2156 grass_gui = read_gui(gisrc, default_gui)
2157 # check that the GUI works but only if not doing a batch job
2158 grass_gui = check_gui(expected_gui=grass_gui)
2159 # save GUI only if we are not doibg batch job
2160 save_gui(gisrc, grass_gui)
2161
2162 # Parsing argument to get LOCATION
2163 if not params.mapset and not params.tmp_location:
2164 # Try interactive startup
2165 # User selects LOCATION and MAPSET if not set
2166 if not set_mapset_interactive(grass_gui):
2167 # No GUI available, update gisrc file
2168 fatal(_("<{0}> requested, but not available. Run GRASS in text "
2169 "mode (--text) or install missing package (usually "
2170 "'grass-gui').").format(grass_gui))
2171 else:
2172 # Try non-interactive start up
2173 if params.tmp_location:
2174 # tmp loc requires other things to be set as well
2175 set_mapset(gisrc=gisrc, geofile=params.geofile,
2176 create_new=True,
2177 tmp_location=params.tmp_location, tmpdir=tmpdir)
2178 elif params.create_new and params.geofile:
2179 set_mapset(gisrc=gisrc, arg=params.mapset,
2180 geofile=params.geofile, create_new=True)
2181 else:
2182 set_mapset(gisrc=gisrc, arg=params.mapset,
2183 create_new=params.create_new)
2184
2185 # Set GISDBASE, LOCATION_NAME, MAPSET, LOCATION from $GISRC
2186 # e.g. wxGUI startup screen writes to the gisrc file,
2187 # so loading it is the only universal way to obtain the values
2188 # this suppose that both programs share the right path to gisrc file
2189 # TODO: perhaps gisrcrc should be removed from here
2190 # alternatively, we can check validity here with all the info we have
2191 # about what was used to create gisrc
2192 mapset_settings = load_gisrc(gisrc, gisrcrc=gisrcrc)
2193
2194 location = mapset_settings.full_mapset
2195
2196 # check and create .gislock file
2197 lock_mapset(mapset_settings.full_mapset, user=user,
2198 force_gislock_removal=params.force_gislock_removal,
2199 grass_gui=grass_gui)
2200 # unlock the mapset which is current at the time of turning off
2201 # in case mapset was changed
2202 atexit.register(lambda: unlock_gisrc_mapset(gisrc, gisrcrc))
2203 # We now own the mapset (set and lock), so we can clean temporary
2204 # files which previous session may have left behind. We do it even
2205 # for first time user because the cost is low and first time user
2206 # doesn't necessarily mean that the mapset is used for the first time.
2207 clean_temp()
2208
2209 # build user fontcap if specified but not present
2210 make_fontcap()
2211
2212 # TODO: is this really needed? Modules should call this when/if required.
2213 ensure_db_connected(location)
2214
2215 # Display the version and license info
2216 # only non-error, interactive version continues from here
2217 if batch_job:
2218 returncode = run_batch_job(batch_job)
2219 clean_all()
2220 sys.exit(returncode)
2221 elif params.exit_grass:
2222 # clean always at exit, cleans whatever is current mapset based on
2223 # the GISRC env variable
2224 clean_all()
2225 sys.exit(0)
2226 else:
2227 clear_screen()
2228 show_banner()
2229 say_hello()
2230 show_info(shellname=shellname,
2231 grass_gui=grass_gui, default_gui=default_gui)
2232 if grass_gui == "wxpython":
2233 message(_("Launching <%s> GUI in the background, please wait...")
2234 % grass_gui)
2235 if sh in ['csh', 'tcsh']:
2236 shell_process = csh_startup(mapset_settings.full_mapset,
2237 mapset_settings.location,
2238 mapset_settings.mapset,
2239 grass_env_file)
2240 elif sh in ['bash', 'msh', 'cygwin']:
2241 shell_process = bash_startup(mapset_settings.full_mapset,
2242 mapset_settings.location,
2243 grass_env_file)
2244 else:
2245 shell_process = default_startup(mapset_settings.full_mapset,
2246 mapset_settings.location)
2247
2248 # start GUI and register shell PID in rc file
2249 start_gui(grass_gui)
2250 kv = read_gisrc(gisrc)
2251 kv['PID'] = str(shell_process.pid)
2252 write_gisrc(kv, gisrc)
2253 exit_val = shell_process.wait()
2254 if exit_val != 0:
2255 warning(_("Failed to start shell '%s'") % os.getenv('SHELL'))
2256
2257 # close GUI if running
2258 close_gui()
2259
2260 # here we are at the end of grass session
2261 clear_screen()
2262 clean_all()
2263 if not params.tmp_location:
2264 writefile(gisrcrc, readfile(gisrc))
2265 # After this point no more grass modules may be called
2266 # done message at last: no atexit.register()
2267 # or register done_message()
2268 done_message()
2269
2270if __name__ == '__main__':
2271 main()
Note: See TracBrowser for help on using the repository browser.