Repository: numpy/numpy

Issue:
f2py fails to generate Fortran modules on Numpy 1.16.0
<!-- Please describe the issue in detail here, and fill in the fields below -->
On Numpy 1.16.0, `f2py` fails to generate Fortran modules:
```
error: f2py target file '/tmp/tmpnke02j8i/src.linux-x86_64-3.6/test_f2py.c' not generated
```

Tested on Ubuntu 1.16 and macOS 1.14.2 both with Python 3.6.
This appears to be a new bug as there are no issue with Numpy 1.15.4 or lower.

### Reproducing code example:

Create `test_f2py.f90` and  `test_f2py.py` and add the following lines:
- `test_f2py.f90`
```
program test_f2py
end program test_f2py
```

- `test_f2py.py`
```
import numpy.f2py as f2py

with open('test_f2py.f90', 'rb') as fid:
    source = fid.read()
f2py.compile(source , modulename='test_f2py', extension='.f90')
```

Run `test_f2py.py`.


Discussion:
May or may not be related to #12756, but certainly seems reasonable to guess that this may be related to the `exec_command()` removal work that included some f2py source. But not sure.
Does not reproduce for me using the wheel on PyPI using Ubuntu 18.04 and using the bash shell. I get a successful production of `./test_f2py.cpython-36m-x86_64-linux-gnu.so`.  Where are you getting numpy from? What is your shell on Ubuntu/Mac OS?
For Ubuntu, please see [this](https://travis-ci.com/psychrometrics/psychrolib/jobs/171421630#L315) on Travis. This uses Ubuntu Xenial (16), Python 3.6.7, Numpy 1.16.0
EDIT: on MacOS, I tried locally and got the same issue. Also using Python 3.6.7, Numpy 1.16.0 (PyPi)
Until someone can reproduce this in an environment that allows debugging the chances of fixing it are very slim.
Mhhh -- I just tried this locally on Ubuntu 18.04.1 LTS and got the same issue:
```bash
testing:/tmp$ python3 test_f2py.py
error: f2py target file '/tmp/tmpe9t9ldo2/src.linux-x86_64-3.6/test_f2pymodule.c' not generated
```

Used [numpy-1.16.0-cp36-cp36m-manylinux1_x86_64.whl](https://files.pythonhosted.org/packages/7b/74/54c5f9bb9bd4dae27a61ec1b39076a39d359b3fb7ba15da79ef23858a9d8/numpy-1.16.0-cp36-cp36m-manylinux1_x86_64.whl).
Just to note I get the same error on current Debian testing (which has 1.16rc2; also tried current master, which gives the same problem). So, at least one system to test on (though I don't have time right now)
My mistake, the `rb'` in `open(..., 'rb')` is the problem. Do we support compiling bytestrings?
[`np.f2py.compile`](https://github.com/numpy/numpy/blob/v1.16.0/numpy/f2py/__init__.py#L35) says `source` is a `str`, but admittedly the documentation is poorly indexed. The docstring does not appear in the [devdocs](http://www.numpy.org/devdocs/f2py/usage.html?highlight=compile) and even the link there is not very discoverable
That's right. The doc strings appear to be the same between 1.15.4 and 1.16 however I would have to pass it as bytestring in 1.15.4 -- have you tried to compile the example passing `source` as `str` in 1.15.4?
On 1.15.4 passing in a `str` raises `TypeError: a bytes-like object is required, not 'str'`
That's good -- we get the same now. Thanks

I think that the doc string in 1.15.4 may be incorrect (see [this](https://github.com/numpy/numpy/blob/v1.15.4/numpy/f2py/__init__.py#L31)) but as the type for `source` has now been changed to `str` in 1.16.0 (see [this](https://github.com/numpy/numpy/blob/v1.16.0/numpy/f2py/__init__.py#L35)) it has lead to some confusion. Regardless, I think that the main issue here is that type has changed from 1.15.4 to 1.16.0, furthermore I agree with your comments you raised about the docs (i.e. "documentation is poorly indexed. The docstring does not appear in the devdocs and even the link there is not very discoverable")

Resolving patch:
diff --git a/numpy/f2py/__init__.py b/numpy/f2py/__init__.py
--- a/numpy/f2py/__init__.py
+++ b/numpy/f2py/__init__.py
@@ -28,12 +28,16 @@ def compile(source,
             extension='.f'
            ):
     """
-    Build extension module from processing source with f2py.
+    Build extension module from a Fortran 77 source string with f2py.
 
     Parameters
     ----------
-    source : str
+    source : str or bytes
         Fortran source of module / subroutine to compile
+
+        .. versionchanged:: 1.16.0
+           Accept str as well as bytes
+
     modulename : str, optional
         The name of the compiled python module
     extra_args : str or list, optional
@@ -55,6 +59,16 @@ def compile(source,
 
         .. versionadded:: 1.11.0
 
+    Returns
+    -------
+    result : int
+        0 on success
+
+    Examples
+    --------
+    .. include:: compile_session.dat
+        :literal:
+
     """
     import tempfile
     import shlex
@@ -67,9 +81,11 @@ def compile(source,
     else:
         fname = source_fn
 
+    if not isinstance(source, str):
+        source = str(source, 'utf-8')
     try:
         with open(fname, 'w') as f:
-            f.write(str(source))
+            f.write(source)
 
         args = ['-c', '-m', modulename, f.name]
 
diff --git a/numpy/f2py/f2py2e.py b/numpy/f2py/f2py2e.py
--- a/numpy/f2py/f2py2e.py
+++ b/numpy/f2py/f2py2e.py
@@ -396,8 +396,25 @@ def dict_append(d_out, d_in):
 
 
 def run_main(comline_list):
-    """Run f2py as if string.join(comline_list,' ') is used as a command line.
-    In case of using -h flag, return None.
+    """
+    Equivalent to running::
+
+        f2py <args>
+
+    where ``<args>=string.join(<list>,' ')``, but in Python.  Unless
+    ``-h`` is used, this function returns a dictionary containing
+    information on generated modules and their dependencies on source
+    files.  For example, the command ``f2py -m scalar scalar.f`` can be
+    executed from Python as follows
+
+    You cannot build extension modules with this function, that is,
+    using ``-c`` is not allowed. Use ``compile`` command instead
+
+    Examples
+    --------
+    .. include:: run_main_session.dat
+        :literal:
+
     """
     crackfortran.reset_global_f2py_vars()
     f2pydir = os.path.dirname(os.path.abspath(cfuncs.__file__))


Test patch:
diff --git a/numpy/f2py/tests/test_regression.py b/numpy/f2py/tests/test_regression.py
--- a/numpy/f2py/tests/test_regression.py
+++ b/numpy/f2py/tests/test_regression.py
@@ -27,3 +27,17 @@ def test_inout(self):
         x = np.arange(3, dtype=np.float32)
         self.module.foo(x)
         assert_equal(x, [3, 1, 2])
+
+@pytest.mark.parametrize('code', [
+        'program test_f2py\nend program test_f2py',
+        b'program test_f2py\nend program test_f2py',
+    ])
+def test_compile(tmpdir, code):
+    # Make sure we can compile str and bytes gh-12796
+    cwd = os.getcwd()
+    try:
+        os.chdir(str(tmpdir))
+        ret = np.f2py.compile(code, modulename='test1_f2py', extension='.f90')
+        assert_equal(ret, 0)
+    finally:
+        os.chdir(cwd)