1 #!/usr/bin/env python2.7
2 # gnetlist - wrapper script for invoking the gEDA netlister
3 # Copyright (C) 1998-2010 Ales Hvezda
4 # Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
5 # Copyright (C) 2013-2020 Roland Lutz
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software Foundation,
19 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 ## Wrapper script for invoking the gEDA netlister.
24 # <tt>xorn netlist</tt> doesn't read the old-style Guile configuration
25 # files \c gafrc and \c gnetlistrc and doesn't support the
26 # Guile-specific command line options \c -l, \c -m, and \c -c.
27 # This wrapper script runs the configuration files, detects the
28 # Guile-specific command line options and translates them into a
29 # <tt>xorn netlist</tt> invocation and can be used as a drop-in
30 # replacement for the old <tt>gnetlist</tt> binary in most situations.
32 # It does *not* read the new-style configuration file \c geda.conf,
33 # and it does *not* support Guile functions as a symbol library.
35 pyexecdir = '@pyexecdir@'
37 gedadatadir = '@gedadatadir@'
38 gedasysconfdir = '@gedasysconfdir@'
39 backenddir = '@backenddir@'
41 import getopt, gettext, os, sys
42 from gettext import gettext as _
43 sys.path.insert(0, pyexecdir)
48 APPEND, PREPEND = xrange(2)
51 ## Expand environment variables in a string.
53 # This function returns the passed string with environment variables
56 # The invocations of environment variables MUST be in the form \c
57 # '${variable_name}'; \c '$variable_name' is not valid here.
58 # Environment variable names can consist solely of letters, digits and
59 # \c '_'. It is possible to escape a \c '$' character in the string
62 # Prints error messages to stderr and leaves the malformed and bad
63 # variable names in the returned string.
65 def expand_env_variables(s):
71 # look for next variable name
80 if j + 1 >= len(s): # '$' is the last character in the string
83 if s[j + 1] == '$': # an escaped '$'
87 if s[j + 1] != '{': # an isolated '$', put it in output
95 # look for the end of the variable name
99 # problem: no closing '}' to variable
100 sys.stderr.write(_("Found malformed environment variable "
102 result += s[i - 2:] # include "${"
105 # test characters of variable name
106 bad_characters = [ch for ch in s[i:j]
107 if not ch.isalnum() and ch != '_']
109 # illegal character detected in variable name
110 sys.stderr.write(_("Found bad character(s) [%s] "
111 "in variable name.\n") % ''.join(bad_characters))
112 result += s[i - 2:j + 1] # include "${" and "}"
116 # extract variable name from string and expand it
118 result += os.environ[s[i:j]]
123 ## Helper function for checking the type of an API function argument.
125 # When constructing an error message, \a fun and \a i are used to
126 # indicate the API function and argument index, respectively.
128 # \throws TypeError if \a arg is not an instance of \a t or of a
131 def check_argument_type(fun, i, arg, t):
132 if not isinstance(arg, t):
133 raise TypeError, '"%s" argument %d must be %s, not %s' % (
134 fun, i, t.__name__, arg.__class__.__name__)
136 ## Helper function for checking whether an API function argument is
139 # When constructing an error message, \a fun and \a i are used to
140 # indicate the API function and argument index, respectively.
142 # \throws TypeError if \a arg is not callable
144 def _check_argument_callable(fun, i, arg):
145 if not callable(arg):
146 raise TypeError, "'%s' argument %d must be callable, " \
147 "but '%s' object is not" % (
148 fun, i, arg.__class__.__name__)
150 # ========================== Configuration options ===========================
152 # general gafrc options
156 # general gafrc options which are not relevant for netlisting
157 bitmap_directory = False
158 bus_ripper_symname = False
159 attribute_promotion = False
160 promote_invisible = False
161 keep_invisible = False
162 always_promote_attributes = False
163 make_backup_files = False
166 hierarchy_refdes_mangle = True
167 hierarchy_netname_mangle = True
168 hierarchy_netattrib_mangle = True
169 hierarchy_refdes_separator = '/'
170 hierarchy_netname_separator = '/'
171 hierarchy_netattrib_separator = '/'
172 hierarchy_refdes_order = APPEND
173 hierarchy_netname_order = APPEND
174 hierarchy_netattrib_order = APPEND
176 # deprecated gnetlistrc options
177 prefer_netname_attribute = False
178 default_net_name = 'unnamed_net'
179 default_bus_name = 'unnamed_bus'
180 traverse_hierarchy = True
182 # -------------------------- RC functions: general ---------------------------
184 ## Add a directory to the Guile load path.
186 # Prepends \a path to the Guile system \c '%load-path', after
187 # expanding environment variables.
189 # \param [in] path path to be added
193 def rc_scheme_directory(path):
194 check_argument_type('scheme-directory', 1, path, basestring)
196 # take care of any shell variables
197 path = expand_env_variables(path)
199 load_path = list(xorn.guile.lookup('%load-path'))
200 load_path.insert(0, path)
201 xorn.guile.define('%load-path', load_path)
207 def rc_world_size(width, height, border):
208 return True # not implemented
210 def rc_print_color_map(map = None):
212 raise NotImplementedError
214 def rc_gnetlist_version(version):
215 check_argument_type('gnetlist-version', 1, version, basestring)
216 return False # this is Xorn, so the versions never match
218 # ------------------------- RC functions: libraries --------------------------
220 # \param [in] name optional descriptive name for library directory
223 # \returns \c True on success, \c False otherwise
225 def rc_component_library(path, name = None):
226 check_argument_type('component-library', 1, path, basestring)
228 check_argument_type('component-library', 2, name, basestring)
230 # take care of any shell variables
231 path = expand_env_variables(path)
234 if not os.path.isdir(path):
236 _("Invalid path [%s] passed to component-library\n") % path)
239 if os.path.isabs(path):
240 symbol_library.extend(['--symbol-library', path])
242 symbol_library.extend(['--symbol-library',
243 os.path.join(os.getcwd(), path)])
247 # \param [in] prefix optional prefix for library names \e (ignored)
249 # \returns \c True on success, \c False otherwise
251 def rc_component_library_search(path, prefix = None):
252 check_argument_type('component-library-search', 1, path, basestring)
253 if prefix is not None:
254 check_argument_type('component-library-search', 2, prefix, basestring)
256 # take care of any shell variables
257 path = expand_env_variables(path)
260 if not os.path.isdir(path):
262 _("Invalid path [%s] passed to component-library-search\n") % path)
265 if os.path.isabs(path):
266 symbol_library.extend(['--symbol-library-search', path])
268 symbol_library.extend(['--symbol-library-search',
269 os.path.join(os.getcwd(), path)])
273 ## Add a library command.
275 # Add a command to the component library.
277 # \param [in] listcmd command to get a list of symbols
278 # \param [in] getcmd command to get a symbol from the library
279 # \param [in] name optional descriptive name for component source
283 def rc_component_library_command(listcmd, getcmd, name):
284 check_argument_type('component-library-command', 1, listcmd, basestring)
285 check_argument_type('component-library-command', 2, getcmd, basestring)
286 check_argument_type('component-library-command', 3, name, basestring)
288 # take care of any shell variables
289 # \bug this may be a security risk!
290 listcmd = expand_env_variables(listcmd)
291 getcmd = expand_env_variables(getcmd)
293 symbol_library.extend(['--symbol-library-command',
294 '%s:%s:%s' % (listcmd, getcmd, name)])
297 ## Add a library function.
299 # Add a set of Guile procedures for listing and generating symbols.
301 # \param [in] listfunc a procedure which takes no arguments and
302 # returns a list of component names
303 # \param [in] getfunc a procedure which takes a component name as an
304 # argument and returns a symbol encoded in a
305 # string in gEDA format, or \c False if the
306 # component name is unknown
307 # \param [in] name a descriptive name for this component source
309 # \returns \c True on success, \c False otherwise
311 # \warning This function is not implemented.
313 def rc_component_library_funcs(listfunc, getfunc, name):
314 check_argument_callable('component-library-funcs', 1, listfunc)
315 check_argument_callable('component-library-funcs', 2, getfunc)
316 check_argument_type('component-library-funcs', 3, name, basestring)
318 raise NotImplementedError
322 def rc_reset_component_library():
323 del symbol_library[:]
326 # \returns \c True on success, \c False otherwise
328 def rc_source_library(path):
329 check_argument_type('source-library', 1, path, basestring)
331 # take care of any shell variables
332 path = expand_env_variables(path)
335 if not os.path.isdir(path):
337 _("Invalid path [%s] passed to source-library\n") % path)
340 if os.path.isabs(path):
341 source_library.extend(['--source-library', path])
343 source_library.extend(['--source-library',
344 os.path.join(os.getcwd(), path)])
348 # \returns \c True on success, \c False otherwise
350 def rc_source_library_search(path):
351 check_argument_type('source-library-search', 1, path, basestring)
353 # take care of any shell variables
354 path = expand_env_variables(path)
357 if not os.path.isdir(path):
359 _("Invalid path [%s] passed to source-library-search\n") % path)
362 if os.path.isabs(path):
363 source_library.extend(['--source-library-search', path])
365 source_library.extend(['--source-library-search',
366 os.path.join(os.getcwd(), path)])
372 def rc_reset_source_library():
373 del source_library[:]
376 # ----------------------------------------------------------------------------
378 # \returns \c True on success, \c False otherwise
380 def rc_bitmap_directory(path):
381 check_argument_type('bitmap-directory', 1, path, basestring)
383 # take care of any shell variables
384 path = expand_env_variables(path)
387 if not os.path.isdir(path):
389 _("Invalid path [%s] passed to bitmap-directory\n") % path)
392 global bitmap_directory
393 bitmap_directory = path
399 def rc_bus_ripper_symname(symname):
400 check_argument_type('bus-ripper-symname', 1, symname, basestring)
402 global bus_ripper_symname
403 bus_ripper_symname = symname
407 def rc_attribute_promotion(mode):
408 check_argument_type('attribute-promotion', 1, mode, basestring)
410 global attribute_promotion
411 attribute_promotion = { 'enabled': True, 'disabled': False }[mode]
415 _("Invalid mode [%s] passed to attribute-promotion\n") % mode)
418 def rc_promote_invisible(mode):
419 check_argument_type('promote-invisible', 1, mode, basestring)
421 global promote_invisible
422 promote_invisible = { 'enabled': True, 'disabled': False }[mode]
426 _("Invalid mode [%s] passed to promote-invisible\n") % mode)
429 def rc_keep_invisible(mode):
430 check_argument_type('keep-invisible', 1, mode, basestring)
432 global keep_invisible
433 keep_invisible = { 'enabled': True, 'disabled': False }[mode]
437 _("Invalid mode [%s] passed to keep-invisible\n") % mode)
442 def rc_always_promote_attributes(attrlist):
443 global always_promote_attributes
445 if isinstance(attrlist, basestring):
447 _("WARNING: using a string for 'always-promote-attributes' is "
448 "deprecated. Use a list of strings instead\n"))
450 # convert the space separated strings into a list
451 always_promote_attributes = [
452 attr for attr in attrlist.split(' ') if attr]
454 check_argument_type('always-promote-attributes', 1, attrlist, tuple)
456 for i, attr in enumerate(attrlist):
457 if not isinstance(attr, basestring):
458 raise TypeError, "'always-promote-attributes' argument 1 " \
459 "index %d must be basestring, not %s" % (
460 i, attr.__class__.__name__)
462 always_promote_attributes = list(attrlist)
466 ## Enable the creation of backup files when saving.
468 # If enabled then a backup file, of the form \c "example.sch~", is
469 # created when saving a file.
471 # \param [in] mode \c "enabled" or \c "disabled"
473 # \returns \c False if \a mode is not a valid mode, \c True if it is
475 def rc_make_backup_files(mode):
476 check_argument_type('make-backup-files', 1, mode, basestring)
478 global make_backup_files
479 make_backup_files = { 'enabled': True, 'disabled': False }[mode]
483 _("Invalid mode [%s] passed to make-backup-files\n") % mode)
486 # ----------------------------------------------------------------------------
488 def rc_hierarchy_uref_mangle(mode):
489 check_argument_type('hierarchy-uref-mangle', 1, mode, basestring)
491 global hierarchy_refdes_mangle
492 hierarchy_refdes_mangle = { 'enabled': True, 'disabled': False }[mode]
495 sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
496 % (mode, 'hierarchy-uref-mangle'))
499 def rc_hierarchy_netname_mangle(mode):
500 check_argument_type('hierarchy-netname-mangle', 1, mode, basestring)
502 global hierarchy_netname_mangle
503 hierarchy_netname_mangle = { 'enabled': True, 'disabled': False }[mode]
506 sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
507 % (mode, 'hierarchy-netname-mangle'))
510 def rc_hierarchy_netattrib_mangle(mode):
511 check_argument_type('hierarchy-netattrib-mangle', 1, mode, basestring)
513 global hierarchy_netattrib_mangle
514 hierarchy_netattrib_mangle = {
515 'enabled': True, 'disabled': False }[mode]
518 sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
519 % (mode, 'hierarchy-netattrib-mangle'))
522 def rc_hierarchy_uref_separator(name):
523 check_argument_type('hierarchy-uref-separator', 1, name, basestring)
525 global hierarchy_refdes_separator
526 hierarchy_refdes_separator = name
530 def rc_hierarchy_netname_separator(name):
531 check_argument_type('hierarchy-netname-separator', 1, name, basestring)
533 global hierarchy_netname_separator
534 hierarchy_netname_separator = name
538 def rc_hierarchy_netattrib_separator(name):
539 check_argument_type('hierarchy-netattrib-separator', 1, name, basestring)
541 global hierarchy_netattrib_separator
542 hierarchy_netattrib_separator = name
546 def rc_hierarchy_netattrib_order(mode):
547 check_argument_type('hierarchy-netattrib-order', 1, mode, basestring)
549 global hierarchy_netattrib_order
550 hierarchy_netattrib_order = {
551 'prepend': PREPEND, 'append': APPEND }[mode]
554 sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
555 % (mode, 'hierarchy-netattrib-order'))
558 def rc_hierarchy_netname_order(mode):
559 check_argument_type('hierarchy-netname-order', 1, mode, basestring)
561 global hierarchy_netname_order
562 hierarchy_netname_order = {
563 'prepend': PREPEND, 'append': APPEND }[mode]
566 sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
567 % (mode, 'hierarchy-netname-order'))
570 def rc_hierarchy_uref_order(mode):
571 check_argument_type('hierarchy-uref-order', 1, mode, basestring)
573 global hierarchy_refdes_order
574 hierarchy_refdes_order = { 'prepend': PREPEND, 'append': APPEND }[mode]
577 sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
578 % (mode, 'hierarchy-uref-order'))
581 # ----------------------------------------------------------------------------
583 def rc_unnamed_netname(name):
584 check_argument_type('unnamed-netname', 1, name, basestring)
585 global default_net_name
586 default_net_name = name
588 def rc_unnamed_busname(name):
589 check_argument_type('unnamed-busname', 1, name, basestring)
590 global default_bus_name
591 default_bus_name = name
593 def rc_hierarchy_traversal(mode):
594 check_argument_type('hierarchy-traversal', 1, mode, basestring)
595 global traverse_hierarchy
596 traverse_hierarchy = mode == 'enabled'
598 def rc_net_naming_priority(mode):
599 check_argument_type('net-naming-priority', 1, mode, basestring)
600 global prefer_netname_attribute
601 prefer_netname_attribute = mode == 'netname'
603 # ============================================================================
605 ## Path to gEDA data shared between all users.
607 # If the \c GEDADATA environment variable is set, this value is used;
608 # otherwise, the configured path is used.
610 if 'GEDADATA' in os.environ and os.environ['GEDADATA']:
611 sys_data_path = os.environ['GEDADATA']
613 sys_data_path = gedadatadir
615 ## Path to gEDA configuration shared between all users.
617 # If the \c GEDADATARC environment variable is set, this value is
618 # used; otherwise, the configured path is used. Finally fallback to
619 # using the system data path.
621 if 'GEDADATARC' in os.environ and os.environ['GEDADATARC']:
622 sys_config_path = os.environ['GEDADATARC']
624 sys_config_path = gedasysconfdir
626 sys_config_path = sys_data_path
628 ## Directory with the gEDA user configuration.
630 # Path to be searched for the current user's gEDA configuration.
631 # Currently defaults to a directory \c ".gEDA" in the user's home
634 user_config_path = os.path.join(os.environ['HOME'], '.gEDA')
636 # ----------------------------------------------------------------------------
638 ## Implement the following functions to add support for geda.conf files.
640 def eda_config_is_loaded(context):
643 def eda_config_load(context):
646 def eda_config_get_filename(context):
649 def eda_config_scm_from_config(context):
652 def eda_config_get_system_context():
655 def eda_config_get_user_context():
658 def eda_config_get_context_for_path(path):
661 def eda_config_get_context_for_file(path):
664 # ----------------------------------------------------------------------------
668 # Loads and runs the Scheme initialisation file \a name.
670 # \param name filename of the RC file to load
671 # \param context configuration context to use while loading
673 def rc1_parse_file(name, context):
674 # skip missing files, directories etc.
675 if not os.path.isfile(name):
678 # If the configuration wasn't loaded yet, attempt to load it.
679 # Config loading is on a best-effort basis; if we fail, just print
682 if not eda_config_is_loaded(context):
683 eda_config_load(context)
685 # Normalizing filenames is hard to do right; don't even try it.
686 #name = f_normalize_filename(name)
688 if '"' in name or '\\' in name:
690 _("ERROR: Illegal character in RC file name: %s\n") % name)
693 # Attempt to load the RC file, if it hasn't been loaded already.
695 xorn.guile.eval_string(
697 ' ((rc-filename-fluid "%s")'
698 ' (rc-config-fluid (eda-config-scm-from-config %s)))'
699 ' (primitive-load "%s"))' % (name, '#f', name))
700 except xorn.guile.GuileError:
701 sys.stderr.write(_("Failed to load RC file [%s]\n") % name)
704 #sys.stderr.write(_("Loaded RC file [%s]\n") % name)
706 ## Return a list of RC file names and associated configuration
709 # Returns a list of pairs <tt>(name, context)</tt> for each Scheme
710 # initialisation files to process: system, user and local (current
711 # working directory), first with the default \c "gafrc" basename and
712 # then with the basename \a rcname, if \a rcname is not \c None;
713 # additionally, \a rcfile if \a rcfile is not \c None.
715 # \param rcname RC file basename, or \c None
716 # \param rcfile specific RC file path, or \c None
718 def rc1_files(rcname, rcfile):
719 # Load RC files in order.
721 (os.path.join(sys_config_path, 'system-gafrc'),
722 eda_config_get_system_context()),
723 (os.path.join(user_config_path, 'gafrc'),
724 eda_config_get_user_context()),
725 ('gafrc', eda_config_get_context_for_path('gafrc'))]
727 # Next application-specific rcname.
728 if rcname is not None:
730 (os.path.join(sys_config_path, 'system-' + rcname),
731 eda_config_get_system_context()),
732 (os.path.join(user_config_path, rcname),
733 eda_config_get_user_context()),
734 (rcname, eda_config_get_context_for_path(rcname))]
736 # Finally, optional additional RC file. Specifically use the
737 # current working directory's configuration context here, no matter
738 # where the rc file is located on disk.
739 if rcfile is not None:
740 result.append((rcfile, eda_config_get_context_for_file(False)))
744 ## General RC file parsing function.
746 # Attempt to load and run system, user and local (current working
747 # directory) Scheme initialisation files, first with the default \c
748 # "gafrc" basename and then with the basename \a rcname, if \a rcname
749 # is not \c None. Additionally, attempt to load and run \a rcfile if
750 # \a rcfile is not \c None.
752 # \param rcname RC file basename, or \c None
753 # \param rcfile specific RC file path, or \c None
755 def rc1_parse(rcname, rcfile):
756 for name, context in rc1_files(rcname, rcfile):
757 rc1_parse_file(name, context)
759 # Re-export function component-library-search (this is
760 # overwritten by geda.scm)
761 xorn.guile.define('component-library-search',
762 rc_component_library_search)
764 # ----------------------------------------------------------------------------
767 'scheme-directory': rc_scheme_directory,
768 'world-size': rc_world_size,
769 'print-color-map': rc_print_color_map,
770 'gnetlist-version': rc_gnetlist_version,
772 'component-library': rc_component_library,
773 'component-library-search': rc_component_library_search,
774 'component-library-command': rc_component_library_command,
775 'component-library-funcs': rc_component_library_funcs,
776 'reset-component-library': rc_reset_component_library,
777 'source-library': rc_source_library,
778 'source-library-search': rc_source_library_search,
779 'reset-source-library': rc_reset_source_library,
781 'bitmap-directory': rc_bitmap_directory,
782 'bus-ripper-symname': rc_bus_ripper_symname,
783 'attribute-promotion': rc_attribute_promotion,
784 'promote-invisible': rc_promote_invisible,
785 'keep-invisible': rc_keep_invisible,
786 'always-promote-attributes': rc_always_promote_attributes,
787 'make-backup-files': rc_make_backup_files,
789 'hierarchy-uref-mangle': rc_hierarchy_uref_mangle,
790 'hierarchy-netname-mangle': rc_hierarchy_netname_mangle,
791 'hierarchy-netattrib-mangle': rc_hierarchy_netattrib_mangle,
792 'hierarchy-uref-separator': rc_hierarchy_uref_separator,
793 'hierarchy-netname-separator': rc_hierarchy_netname_separator,
794 'hierarchy-netattrib-separator': rc_hierarchy_netattrib_separator,
795 'hierarchy-netattrib-order': rc_hierarchy_netattrib_order,
796 'hierarchy-netname-order': rc_hierarchy_netname_order,
797 'hierarchy-uref-order': rc_hierarchy_uref_order,
799 'unnamed-netname': rc_unnamed_netname,
800 'unnamed-busname': rc_unnamed_busname,
801 'hierarchy-traversal': rc_hierarchy_traversal,
802 'net-naming-priority': rc_net_naming_priority,
804 'eda-config-is-loaded': eda_config_is_loaded,
805 'eda-config-load': eda_config_load,
806 'eda-config-get-filename': eda_config_get_filename,
807 'eda-config-scm-from-config': eda_config_scm_from_config,
808 'eda-config-get-system-context': eda_config_get_system_context,
809 'eda-config-get-user-context': eda_config_get_user_context,
810 'eda-config-get-context-for-path': eda_config_get_context_for_path,
811 'eda-config-get-context-for-file': eda_config_get_context_for_file,
813 xorn.guile.define(name, value)
815 xorn.guile.eval_string('''
816 (define rc-filename-fluid (make-unbound-fluid))
817 (define rc-config-fluid (make-unbound-fluid))
819 ;;; Get the filename of the RC file being evaluated.
821 ;;; \returns the full path to the RC file if the interpreter can
822 ;;; resolve the filename, otherwise throws an error
824 (define (rc-filename) (fluid-ref rc-filename-fluid))
826 ;;; Get a configuration context for the current RC file.
828 ;;; Returns the configuration context applicable to the RC file being
829 ;;; evaluated. This function is intended to support gEDA transition
830 ;;; from functions in RC files to static configuration files.
832 ;;; \returns an EdaConfig smob
834 (define (rc-config) (fluid-ref rc-config-fluid))
837 # ----------------------------------------------------------------------------
839 ## Get a sorted list of available backends.
841 # Returns a list of available netlister backends by searching for
842 # files in each of the directories in the current Guile \c %load-path.
843 # A file is considered to be a netlister backend if its name begins
844 # with \c "gnet-" and ends with \c ".scm".
847 # Look up the current Guile %load-path
848 load_path = xorn.guile.lookup('%load-path')
850 backend_names = set()
852 # add-to-load-path adds paths twice (once when it is compiled and
853 # once when it is run), so we need to filter out duplicate paths.
854 visited_dirnames = set()
856 for dir_name in load_path:
857 if dir_name in visited_dirnames:
859 visited_dirnames.add(dir_name)
862 d_names = os.listdir(dir_name)
864 #sys.stderr.write(_("Can't open directory %s: %s\n")
865 # % (dir_name, e.strerror))
868 for d_name in d_names:
869 # Check that filename has the right format to be a backend
870 if d_name.startswith('gnet-') and d_name.endswith('.scm'):
871 # Remove prefix & suffix. Add to list of backend names.
872 backend_names.add(d_name[5:-4])
874 # Sort the list of backends
875 return sorted(backend_names)
878 gettext.bindtextdomain(xorn.config.PACKAGE, xorn.config.localedir)
879 gettext.textdomain(xorn.config.PACKAGE)
882 options, args = getopt.getopt(
883 xorn.command.args, 'c:g:hil:L:m:o:O:p:qvV', [
884 'quiet', 'verbose', 'interactive', 'report-gui',
885 'list-backends', 'help', 'version'])
886 except getopt.GetoptError as e:
887 xorn.command.invalid_arguments(e.msg)
891 output_filename = 'output.net'
892 guile_proc = None # Guile netlist backend to use
893 python_backend = None # Python netlist backend to use
894 interactive_mode = False # enter interactive Scheme REPL after loading
895 backend_options = [] # option strings passed to backend
897 evaluate_at_startup = []
898 extra_load_paths = [os.path.join(sys_data_path, 'scheme')]
899 load_before_backend = []
900 load_after_backend = []
903 list_backends_ = False
905 for option, value in options:
906 if option == '-q' or option == '--quiet':
908 elif option == '-v' or option == '--verbose':
911 output_filename = value
913 # Argument is a directory to add to the Scheme load path
914 extra_load_paths.append(value)
918 python_backend = value.replace('-', '_')
920 backend_options.append(value)
922 # Argument is filename of a Scheme script to be run before
923 # loading gnetlist backend
924 load_before_backend.append(value)
926 # Argument is filename of a Scheme script to be run after
927 # loading gnetlist backend
928 load_after_backend.append(value)
930 # Evaluate Scheme expression at startup
931 evaluate_at_startup.append(value)
932 elif option == '-i' or option == '--interactive':
933 interactive_mode = True
934 elif option == '--report-gui':
936 elif option == '--list-backends':
937 list_backends_ = True
939 elif option == '-h' or option == '--help':
940 sys.stdout.write('''\
941 Usage: %s [OPTION]... -g BACKEND [--] FILE...
942 %s [OPTION]... -i [--] FILE...
943 Generate a netlist from one or more gEDA schematic files.
946 -v, --verbose verbose mode
947 -o FILE filename for netlist data output
948 -L DIR add DIR to Python and Scheme search path
949 -p BACKEND specify Python netlist backend to use
950 -g BACKEND specify Scheme netlist backend to use
951 -O STRING pass an option string to backend
952 -l FILE load Scheme file before loading backend
953 -m FILE load Scheme file after loading backend
954 -c EXPR evaluate Scheme expression at startup
955 -i enter interactive Python interpreter after loading
956 --report-gui report warnings and errors in GUI dialog
957 --list-backends print a list of available netlist backends
958 -h, --help help; this message
959 -V, --version show version information
960 -- treat all remaining arguments as filenames
963 gEDA/gaf homepage: http://www.geda-project.org/
964 ''' % (xorn.command.program_name, xorn.command.program_name,
965 xorn.config.PACKAGE_NAME, xorn.config.PACKAGE_BUGREPORT))
968 elif option == '-V' or option == '--version':
969 sys.stdout.write('''\
970 gnetlist - gEDA netlister (%s)
971 Copyright (C) 1998-2020 gEDA developers
973 This program is free software; you can redistribute it and/or
974 modify it under the terms of the GNU General Public License
975 as published by the Free Software Foundation; either version 2
976 of the License, or (at your option) any later version.
978 This program is distributed in the hope that it will be useful,
979 but WITHOUT ANY WARRANTY; without even the implied warranty of
980 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
981 GNU General Public License for more details.
982 ''' % xorn.config.PACKAGE_STRING)
985 if guile_proc is not None and python_backend is not None:
987 _("Options `-g BACKEND' and `-p BACKEND' are mutually exclusive\n"))
990 if guile_proc is None and python_backend is None \
991 and not interactive_mode and not list_backends_:
993 _("You gave neither backend to execute nor interactive mode!\n"))
996 # --------------------------------------------------------------------
998 # 1. Evaluate expressions passed as `-c' options
999 for value in evaluate_at_startup:
1001 xorn.guile.eval_string(value)
1002 except xorn.guile.GuileError:
1003 sys.stderr.write(_("An error occurred while evaluating a "
1004 "`-c' option:\n%s\n") % value)
1007 # 2. Add paths passed as `-L' options to the Guile load path.
1008 for extra_load_path in extra_load_paths:
1009 if '"' in extra_load_path or '\\' in extra_load_path:
1010 sys.stderr.write(_("Illegal character in extra load path:\n"
1011 " %s\n") % extra_load_path)
1013 xorn.guile.eval_string('(add-to-load-path "%s")' % extra_load_path)
1016 if '"' in sys_data_path or '\\' in sys_data_path:
1017 sys.stderr.write(_("ERROR: Illegal character in "
1018 "system data path: %s\n") % sys_data_path)
1021 if '"' in sys_config_path or '\\' in sys_config_path:
1022 sys.stderr.write(_("ERROR: Illegal character in "
1023 "system config path: %s\n") % sys_config_path)
1026 xorn.guile.eval_string('''
1027 (define (eval-protected exp)
1028 (eval exp (interaction-environment)))
1031 xorn.guile.eval_string('''
1032 (define-module (geda core os))
1033 (define-public (%%platform) '(linux))
1034 (define-public (%%sys-data-dirs) '("%s"))
1035 (define-public (%%sys-config-dirs) '("%s"))
1036 (define-public (%%user-data-dir) "%s")
1037 (define-public (%%user-config-dir) "%s")
1039 ''' % (sys_data_path, sys_config_path, user_config_path, user_config_path))
1041 # prevent loading of actual "geda deprecated" module
1042 xorn.guile.eval_string('(define-module (geda deprecated)) #f')
1044 xorn.guile.define('use-legacy-frontend', False)
1045 rc1_parse('gnetlistrc', None)
1047 # 4. Print a list of available netlist backends if requested.
1049 import gaf.netlist.backend
1050 gaf.netlist.backend.load_path.insert(0, backenddir)
1051 for extra_load_path in extra_load_paths[1:]:
1052 gaf.netlist.backend.load_path.insert(0, extra_load_path)
1054 python_backends = set(name.replace('_', '-')
1055 for name in gaf.netlist.backend.list_backends())
1056 guile_backends = set(list_backends())
1058 sys.stdout.write(_("List of available backends:\n\n"))
1059 for name in sorted(set.union(guile_backends, python_backends)):
1060 sys.stdout.write(" [%s%s]\t%s\n" % (
1061 'P' if name in python_backends else ' ',
1062 'G' if name in guile_backends else ' ', name))
1063 sys.stdout.write("\n")
1066 # 5. Evaluate the first set of Scheme expressions before we load
1067 # any schematic files
1068 guile_sources = load_before_backend[:]
1070 # 6. Load basic gnetlist functions
1071 search_load_path = xorn.guile.lookup('%search-load-path')
1072 fn = search_load_path('gnetlist.scm')
1075 "ERROR: Could not find `gnetlist.scm' in load path.\n"))
1077 guile_sources.append(fn)
1079 # Anything up to and including the `-m' options may influence the
1080 # library paths, but loading some backends (systemc, verilog) at
1081 # this point causes trouble. So don't run backend and `-m'
1082 # options here and hope neither sets any vital paths.
1083 for guile_source in guile_sources:
1085 xorn.guile.load(guile_source)
1086 except xorn.guile.GuileError:
1087 sys.stderr.write(_("Error loading \"%s\"\n") % guile_source)
1090 if guile_proc is not None:
1091 # 7. Load backend code
1092 fn = search_load_path('gnet-%s.scm' % guile_proc)
1095 "ERROR: Could not find backend `%s' in load path.\n\n"
1096 "Run `%s --list-backends' for a full list of available "
1097 "backends.\n") % (guile_proc, xorn.command.program_name))
1099 guile_sources.append(fn)
1101 # 8. Evaluate second set of Scheme expressions
1102 guile_sources += load_after_backend
1103 # `-m' options aren't executed unless `-g' is specified
1105 # 9. Run post-traverse code
1106 fn = search_load_path('gnetlist-post.scm')
1109 "ERROR: Could not find `gnetlist-post.scm' in load path.\n"))
1111 guile_sources.append(fn)
1113 # --------------------------------------------------------------------
1115 cmd = [XORN, 'netlist'] + symbol_library + source_library
1117 if prefer_netname_attribute:
1118 cmd.append('--net-naming-priority=netname-attribute')
1119 if default_net_name != 'unnamed_net':
1120 cmd.append('--default-net-name=' + default_net_name)
1121 if default_bus_name != 'unnamed_bus':
1122 cmd.append('--default-bus-name=' + default_bus_name)
1123 if not traverse_hierarchy:
1124 cmd.append('--dont-traverse-hierarchy')
1125 if not hierarchy_refdes_mangle:
1126 cmd.append('--hierarchy-refdes-mangle=disabled')
1127 if not hierarchy_netname_mangle:
1128 cmd.append('--hierarchy-netname-mangle=disabled')
1129 if not hierarchy_netattrib_mangle:
1130 cmd.append('--hierarchy-netattrib-mangle=disabled')
1131 if hierarchy_refdes_separator != '/':
1133 '--hierarchy-refdes-separator=' + hierarchy_refdes_separator)
1134 if hierarchy_refdes_order != APPEND:
1135 cmd.append('--hierarchy-refdes-order=prepend')
1136 if hierarchy_netname_separator != '/':
1138 '--hierarchy-netname-separator=' + hierarchy_netname_separator)
1139 if hierarchy_netname_order != APPEND:
1140 cmd.append('--hierarchy-netname-order=prepend')
1142 if python_backend is None:
1143 cmd += ['-g', 'guile']
1145 for value in evaluate_at_startup:
1146 cmd += ['-O', 'eval=' + value]
1147 for value in extra_load_paths:
1148 cmd += ['-O', 'add-to-load-path=' + value]
1149 for value in guile_sources:
1150 cmd += ['-O', 'load=' + value]
1152 if guile_proc is not None and not interactive_mode:
1153 # if both `-i' and `-g' are specified, the backend is
1154 # loaded but not executed (don't ask me, that's what the
1155 # old gnetlist code does)
1156 cmd += ['-O', 'guile-proc=' + guile_proc]
1158 if guile_proc.startswith('spice'):
1159 cmd += ['-O', 'use-spice-netnames']
1162 cmd += ['-O', 'verbosity=1']
1164 cmd += ['-O', 'verbosity=-1']
1168 for backend_option in backend_options:
1169 cmd += ['-O', backend_option]
1171 for extra_load_path in extra_load_paths[1:]:
1172 cmd += ['-L', extra_load_path]
1173 cmd += ['-g', python_backend]
1174 for backend_option in backend_options:
1175 cmd += ['-O', backend_option]
1177 if guile_proc is not None and guile_proc.startswith('drc'):
1178 cmd.append('--ignore-errors')
1180 if guile_proc == 'pcbfwd' or report_gui:
1181 cmd.append('--report-gui')
1184 # gnetlist prints schematic names unless `-q' has been specified
1187 if interactive_mode:
1190 if output_filename != '-':
1191 cmd += ['-o', output_filename]
1196 sys.stderr.write('%s\n' % ' '.join(cmd))
1198 os.execv(cmd[0], cmd)
1200 if __name__ == '__main__':