Repository: numba/numba

Issue:
ARMv8 cuda findlib issue
### Versions
* `python 3.6`
* `llvmlite 0.28.0.dev0+41.gd5fadcc` (compiled from source, current master)
* `numba==0.43.0.dev0+577.g1bfb050f4` (compiled from source, current master)

### System
* ARM v8.2 with custom Ubuntu 18.04 (see [Jetson Xavier](https://developer.nvidia.com/embedded/buy/jetson-agx-xavier-devkit))

### Minimum working reproducer
```
import numba
from numba import cuda

@cuda.jit('(int64, float32)')
def cuda_issue(dd, ff):
    test = dd + ff

cuda_issue(1,1.1)
```

### Error message:
```
Traceback (most recent call last):
  File "cuda_issue.py", line 4, in <module>
    @cuda.jit('(int64, float32)')
  File "numba/numba/cuda/decorators.py", line 98, in kernel_jit
    kernel.bind()
  File "numba/numba/cuda/compiler.py", line 503, in bind
    self._func.get()
  File "numba/numba/cuda/compiler.py", line 381, in get
    ptx = self.ptx.get()
  File "numba/numba/cuda/compiler.py", line 352, in get
    **self._extra_options)
  File "numba/numba/cuda/cudadrv/nvvm.py", line 475, in llvm_to_ptx
    libdevice = LibDevice(arch=opts.get('arch', 'compute_20'))
  File "numba/numba/cuda/cudadrv/nvvm.py", line 350, in __init__
    if get_libdevice(arch) is None:
  File "numba/numba/cuda/cudadrv/libs.py", line 22, in get_libdevice
    candidates = find_file(re.compile(pat), libdir)
  File "numba/numba/findlib.py", line 45, in find_file
    entries = os.listdir(ldir)
FileNotFoundError: [Errno 2] No such file or directory: 'u'
```

### Additional information

* Bug was not present in `numba==0.42.0`, using the above mentioned version of `llvmlite`.
* I currently am running the `numba` tests on the machine but they take quite a while. I can upload the logs for both `0.42.0` and the most recent version if that would help to also resolve some other issues.


Discussion:
Thanks for the report, I've a feeling I know what's causing this. Some changes to library finding were needed to accommodate some necessary alterations in the latest builds of the `cudatoolkit` package on windows. I think when I patched it I didn't anticipate code paths that would end up with a a single string as the `libdir` argument, so it ends up doing `list(<string>)` hence the single char as the search dir.
In fact, this reproduces on `x86`:
```
NUMBAPRO_LIBDEVICE="/usr/local/somelib.so" python issue3831.py 
```

Resolving patch:
diff --git a/numba/findlib.py b/numba/findlib.py
--- a/numba/findlib.py
+++ b/numba/findlib.py
@@ -38,6 +38,8 @@ def find_lib(libname, libdir=None, platform=None):
 def find_file(pat, libdir=None):
     if libdir is None:
         libdirs = get_lib_dirs()
+    elif isinstance(libdir, str):
+        libdirs = [libdir,]
     else:
         libdirs = list(libdir)
     files = []


Test patch:
diff --git a/numba/cuda/tests/cudadrv/test_detect.py b/numba/cuda/tests/cudadrv/test_detect.py
--- a/numba/cuda/tests/cudadrv/test_detect.py
+++ b/numba/cuda/tests/cudadrv/test_detect.py
@@ -1,8 +1,13 @@
 from __future__ import absolute_import, print_function
+import os
+import sys
+import subprocess
+import threading
 from numba import cuda
-from numba.cuda.testing import unittest, SerialMixin
+from numba.cuda.testing import unittest, SerialMixin, skip_on_cudasim
 from numba.tests.support import captured_stdout
 
+
 class TestCudaDetect(SerialMixin, unittest.TestCase):
     def test_cuda_detect(self):
         # exercise the code path
@@ -13,5 +18,64 @@ def test_cuda_detect(self):
         self.assertIn('CUDA devices', output)
 
 
+class TestCUDAFindLibs(SerialMixin, unittest.TestCase):
+
+    def run_cmd(self, cmdline, env):
+        popen = subprocess.Popen(cmdline,
+                                 stdout=subprocess.PIPE,
+                                 stderr=subprocess.PIPE,
+                                 env=env)
+
+        # finish in 5 minutes or kill it
+        timeout = threading.Timer(5 * 60., popen.kill)
+        try:
+            timeout.start()
+            out, err = popen.communicate()
+            # the process should exit with an error
+            return out.decode(), err.decode()
+        finally:
+            timeout.cancel()
+        return None, None
+
+    def run_test_in_separate_process(self, envvar, envvar_value):
+        env_copy = os.environ.copy()
+        env_copy[envvar] = str(envvar_value)
+        code = """if 1:
+            from numba import cuda
+            @cuda.jit('(int64,)')
+            def kernel(x):
+                pass
+            kernel(1,)
+            """
+        cmdline = [sys.executable, "-c", code]
+        return self.run_cmd(cmdline, env_copy)
+
+    @skip_on_cudasim('Simulator does not hit device library search code path')
+    @unittest.skipIf(not sys.platform.startswith('linux'), "linux only")
+    def test_cuda_find_lib_errors(self):
+        """
+        This tests that the find_libs works as expected in the case of an
+        environment variable being used to set the path.
+        """
+        # one of these is likely to exist on linux, it's also unlikely that
+        # someone has extracted the contents of libdevice into here!
+        locs = ['lib', 'lib64']
+
+        looking_for = None
+        for l in locs:
+            looking_for = os.path.join(os.path.sep, l)
+            if os.path.exists(looking_for):
+                break
+
+        # This is the testing part, the test will only run if there's a valid
+        # path in which to look
+        if looking_for is not None:
+            out, err = self.run_test_in_separate_process("NUMBAPRO_LIBDEVICE",
+                                                         looking_for)
+            self.assertTrue(out is not None)
+            self.assertTrue(err is not None)
+            self.assertIn("Missing libdevice file for", str(err))
+
+
 if __name__ == '__main__':
     unittest.main()