Repository: Parquery/pylddwrap

Issue:
pylddwrap 1.1.0 fails to handle statically linked libraries
I have noticed that with the latest release of 1.1.0, the `lddwrap.list_dependencies` fails when using it on a statically linked library:

```
my_static_lib.so:
        statically linked
```

with the error message: 

> RuntimeError: Unexpected mem address. Expected to match ^\s*\(([^)]*)\)\s*$, but got: 'linked'

This makes sense because normally, for a shared library, this is what you would get:
 
```
my_share_lib.so:
        linux-lib.so.1 (0x00007fff4a9fe000)
        linux-lib2.so => path
```

so `statically linked` is being parsed in the way that `statically` is considered to be the file and the `linked` is considered to be a memory address which of course fails the regex match.

Do you think that it would make more sense to avoid proceeding to the parsing step if `ldd` tells us that it's a statically linked library? 

For the previous version, 1.0.1, it's happily reporting the following:

```
from pathlib import Path
import lddwrap
deps = lddwrap.list_dependencies(Path('my_static_lib.so'))
for dep in deps:
    print(dep.path)
    print(dep.found)
    print(dep.soname)

statically
True
None
```

The statically is definitely not a correct file path, but raising an exception does not feel right either as `ldd` doesn't fail itself.


Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 61](https://coveralls.io/builds/35402234)

* **5** of **6**   **(83.33%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.4%**) to **91.716%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [lddwrap/__init__.py](https://coveralls.io/builds/35402234/source?filename=lddwrap%2F__init__.py#L258) | 5 | 6 | 83.33%
<!-- | **Total:** | **5** | **6** | **83.33%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/35402234/badge)](https://coveralls.io/builds/35402234) |
| :-- | --: |
| Change from base [Build 58](https://coveralls.io/builds/35258436): |  -0.4% |
| Covered Lines: | 155 |
| Relevant Lines: | 169 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/lddwrap/__init__.py b/lddwrap/__init__.py
index d1a2d87..520bc07 100644
--- a/lddwrap/__init__.py
+++ b/lddwrap/__init__.py
@@ -252,10 +252,18 @@ def _cmd_output_parser(cmd_out: str) -> List[Dependency]:
     """
     dependencies = []  # type: List[Dependency]
 
-    for line in [
-            line.strip() for line in cmd_out.split('\n') if line.strip() != ''
-    ]:
+    lines = [line.strip() for line in cmd_out.split('\n') if line.strip() != '']
+
+    if len(lines) == 0:
+        return []
+
+    # This is a special case of a static library. The first line refers
+    # to the library and the second line indicates that the library
+    # was statically linked.
+    if len(lines) == 2 and lines[1] == 'statically linked':
+        return []
 
+    for line in lines:
         dep = _parse_line(line=line)
         if dep is not None:
             dependencies.append(dep)


Test patch:
diff --git a/tests/test_ldd.py b/tests/test_ldd.py
index 45e6c8d..2a47981 100644
--- a/tests/test_ldd.py
+++ b/tests/test_ldd.py
@@ -2,6 +2,7 @@
 """Test lddwrap."""
 # pylint: disable=missing-docstring,too-many-public-methods
 import pathlib
+import tempfile
 import textwrap
 import unittest
 from typing import Any, List, Optional
@@ -140,6 +141,17 @@ class TestParseOutputWithoutUnused(unittest.TestCase):
             'Expected 2 parts in the line but found {}: {}'.format(
                 line.count(' ') + 1, line), str(run_err))
 
+    def test_parse_static(self) -> None:
+        """Test parsing of the output when we ldd a static library."""
+        # pylint: disable=protected-access
+        deps = lddwrap._cmd_output_parser(
+            textwrap.dedent('''\
+            my_static_lib.so:
+                statically linked
+            '''))
+
+        self.assertListEqual([], deps)
+
 
 class TestAgainstMockLdd(unittest.TestCase):
     def test_pwd(self):
@@ -308,6 +320,24 @@ class TestAgainstMockLdd(unittest.TestCase):
                     [], diff_dependencies(ours=dep, theirs=exp_dep),
                     "Mismatch at the unused dependency {}".format(i))
 
+    def test_with_static_library(self) -> None:
+        """Test against a fantasy static library."""
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            lib_pth = pathlib.Path(tmp_dir) / "my_static_lib.so"
+            lib_pth.write_text("totally static!")
+
+            with tests.MockLdd(
+                    out=textwrap.dedent('''\
+                                my_static_lib.so:
+                                \tstatically linked\n'''),
+                    out_unused=''):
+                # pylint: enable=line-too-long
+                deps = lddwrap.list_dependencies(path=lib_pth, unused=True)
+
+                # The dependencies are empty since the library is
+                # statically linked.
+                self.assertListEqual([], deps)
+
 
 class TestSorting(unittest.TestCase):
     def test_sorting_by_all_attributes(self) -> None: