44import collections
55import copy
66import json
7+ import os
78import pathlib
89import re
910import subprocess
@@ -87,34 +88,13 @@ def as_mapping(self) -> Mapping[str, Any]:
8788 ("unused" , self .unused )])
8889
8990
90- _MEM_ADDRESS_RE = re .compile (r'^\s*\(([^)]*)\)\s*$' )
91-
92-
93- def _strip_mem_address (text : str ) -> str :
94- r"""
95- Strip the space and brackets from the mem address in the output.
96-
97- :param text: to be stripped
98- :return: bare mem address
99-
100- >>> _strip_mem_address('(0x00007f9a1a329000)')
101- '0x00007f9a1a329000'
102-
103- >>> _strip_mem_address(' (0x00007f9a1a329000) ')
104- '0x00007f9a1a329000'
105-
106- >>> _strip_mem_address('\t(0x00007f9a1a329000)\t')
107- '0x00007f9a1a329000'
108- """
109- mtch = _MEM_ADDRESS_RE .match (text )
110- if not mtch :
111- raise RuntimeError (("Unexpected mem address. Expected to match {}, "
112- "but got: {!r}" ).format (_MEM_ADDRESS_RE .pattern ,
113- text ))
114-
115- return mtch .group (1 )
91+ _LDD_ARROW_OUTPUT_RE = re .compile (
92+ r"(?P<soname>.+)\s=>\s(?P<dep_path>.*)\s\(?(?P<mem_address>\w*)\)?" )
93+ _LDD_NON_ARROW_OUTPUT_RE = re .compile (
94+ r"(?P<dep_path>.+)\s\(?(?P<mem_address>\w*)\)?" )
11695
11796
97+ # pylint: disable=too-many-branches
11898def _parse_line (line : str ) -> Optional [Dependency ]:
11999 """
120100 Parse single line of ldd output.
@@ -123,8 +103,6 @@ def _parse_line(line: str) -> Optional[Dependency]:
123103 :return: dependency or None if line was empty
124104
125105 """
126- found = not 'not found' in line
127- parts = [part .strip () for part in line .split (' ' )]
128106 # pylint: disable=line-too-long
129107 # There are two types of outputs for a dependency, with or without soname.
130108 # The VDSO is a special case (see https://man7.org/linux/man-pages/man7/vdso.7.html)
@@ -137,51 +115,55 @@ def _parse_line(line: str) -> Optional[Dependency]:
137115 # with soname but not found: 'libboost_program_options.so.1.62.0 => not found'
138116 # with soname but without rpath: 'linux-vdso.so.1 => (0x00007ffd7c7fd000)'
139117 # pylint: enable=line-too-long
118+ found = not 'not found' in line
119+ soname = None
120+ dep_path = None
121+ mem_address = None
140122 if '=>' in line :
141- if len (parts ) != 4 :
142- raise RuntimeError (
143- "Expected 4 parts in the line but found {}: {}" .format (
144- len (parts ), line ))
145-
146- soname = None
147- dep_path = None
148- mem_address = None
123+ mtch = _LDD_ARROW_OUTPUT_RE .match (line )
124+ if not mtch :
125+ raise RuntimeError (("Unexpected ldd output. Expected to match {}, "
126+ "but got: {!r}" ).format (
127+ _LDD_ARROW_OUTPUT_RE .pattern , line ))
149128 if found :
150- soname = parts [ 0 ]
151- if parts [ 2 ] != '' :
152- dep_path = pathlib .Path (parts [ 2 ])
153-
154- mem_address = _strip_mem_address ( text = parts [ 3 ])
129+ soname = mtch [ "soname" ]
130+ if mtch [ "dep_path" ] :
131+ dep_path = pathlib .Path (mtch [ "dep_path" ])
132+ if mtch [ "mem_address" ]:
133+ mem_address = mtch [ "mem_address" ]
155134 else :
156- if "/" in parts [0 ]:
157- dep_path = pathlib .Path (parts [0 ])
135+ if os .sep in mtch ["soname" ]:
136+ # This is a special case where the dep_path comes before the
137+ # arrow and we have no soname
138+ dep_path = pathlib .Path (mtch ["soname" ])
158139 else :
159- soname = parts [0 ]
160-
161- return Dependency (
162- soname = soname , path = dep_path , found = found , mem_address = mem_address )
140+ soname = mtch ["soname" ]
163141 else :
164- if len (parts ) != 2 :
165- # Please see https://github.com/Parquery/pylddwrap/pull/14
166- if 'no version information available' in line :
167- return None
142+ # Please see https://github.com/Parquery/pylddwrap/pull/14
143+ if 'no version information available' in line :
144+ return None
145+
146+ mtch = _LDD_NON_ARROW_OUTPUT_RE .match (line )
147+ if not mtch :
148+ raise RuntimeError (("Unexpected ldd output. Expected to match {}, "
149+ "but got: {!r}" ).format (
150+ _LDD_NON_ARROW_OUTPUT_RE .pattern , line ))
151+ # Special case for linux-vdso
152+ if mtch ["dep_path" ].startswith ("linux-vdso" ):
153+ soname = mtch ["dep_path" ]
154+ else :
155+ dep_path = pathlib .Path (mtch ["dep_path" ])
168156
169- raise RuntimeError (
170- "Expected 2 parts in the line but found {}: {}" .format (
171- len (parts ), line ))
157+ found = True
158+ mem_address = mtch ["mem_address" ]
172159
173- if parts [0 ].startswith ('linux-vdso' ):
174- soname = parts [0 ]
175- path = None
176- else :
177- soname = None
178- path = pathlib .Path (parts [0 ])
179-
180- return Dependency (
181- soname = soname ,
182- path = path ,
183- found = True ,
184- mem_address = _strip_mem_address (text = parts [1 ]))
160+ # Sanity check to see if it didn't parse garbage:
161+ # dep_path should have at least a `/` somewhere in the filepath
162+ if dep_path and os .sep not in str (dep_path ):
163+ raise RuntimeError ("Unexpected library path: {}" .format (dep_path ))
164+
165+ return Dependency (
166+ soname = soname , path = dep_path , found = found , mem_address = mem_address )
185167
186168
187169@icontract .require (lambda path : path .is_file ())
0 commit comments