Skip to content

Commit

Permalink
Fix clean up of downloaded modules when using go.mod
Browse files Browse the repository at this point in the history
Projects that use go.mod will result in the build downloading the
modules to an temporary path. Golang introduced read only directories to
protect the generated module cache from accidental writes, resulting in
failures to build when exiting the temporary directory context.

Adapt the solution from pre-commit to make the directory and file
writable on removal and include a rudimentary test that exercises
download via a simple example module hosted by the golang org in github.

Fixes asottile#49
  • Loading branch information
electrofelix committed Sep 22, 2020
1 parent a6eaed0 commit b9fd68b
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 10 deletions.
34 changes: 24 additions & 10 deletions setuptools_golang.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import argparse
import contextlib
import copy
import errno
import os
import shlex
import shutil
Expand All @@ -10,6 +11,8 @@
import tempfile
from distutils.ccompiler import CCompiler
from distutils.dist import Distribution
from types import TracebackType
from typing import Any
from typing import Callable
from typing import Dict
from typing import Generator
Expand All @@ -22,22 +25,33 @@
from setuptools.command.build_ext import build_ext as _build_ext


def rmtree(path: str) -> None:
"""On windows, rmtree fails for readonly dirs."""
def handle_remove_readonly(
func: Callable[..., Any],
path: str,
exc: Tuple[Type[OSError], OSError, TracebackType],
) -> None:
excvalue = exc[1]
if (
func in (os.rmdir, os.remove, os.unlink) and
excvalue.errno == errno.EACCES
):
for p in (path, os.path.dirname(path)):
os.chmod(p, os.stat(p).st_mode | stat.S_IWUSR)
func(path)
else:
raise
shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly)


@contextlib.contextmanager
def _tmpdir() -> Generator[str, None, None]:
tempdir = tempfile.mkdtemp()
try:
yield tempdir
finally:
def err(
action: Callable[[str], None],
name: str,
exc: Exception,
) -> None: # pragma: no cover (windows)
"""windows: can't remove readonly files, make them writeable!"""
os.chmod(name, stat.S_IWRITE)
action(name)

shutil.rmtree(tempdir, onerror=err)
rmtree(tempdir)


def _get_cflags(
Expand Down
5 changes: 5 additions & 0 deletions testing/gomodules/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/asottile/setuptools-golang/testing/gomodules

go 1.14

require github.com/golang/example v0.0.0-20170904185048-46695d81d1fa
2 changes: 2 additions & 0 deletions testing/gomodules/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/golang/example v0.0.0-20170904185048-46695d81d1fa h1:iqCQC2Z53KkwGgTN9szyL4q0OQHmuNjeoNnMT6lk66k=
github.com/golang/example v0.0.0-20170904185048-46695d81d1fa/go.mod h1:tO/5UvQ/uKigUjQBPqzstj6uxd3fUIjddi19DxGJeWg=
19 changes: 19 additions & 0 deletions testing/gomodules/reversemsg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

// #include <Python.h>
import "C"

import (
"fmt"

"github.com/golang/example/stringutil"
)

//export reversemsg
func reversemsg() *C.PyObject {
fmt.Print(stringutil.Reverse("elpmaxe tset"))

return C.Py_None
}

func main() {}
29 changes: 29 additions & 0 deletions testing/gomodules/reversemsg_support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

// #include <Python.h>
//
// PyObject* reversemsg();
//
// static struct PyMethodDef methods[] = {
// {"reversemsg", (PyCFunction)reversemsg, METH_NOARGS},
// {NULL, NULL}
// };
//
// #if PY_MAJOR_VERSION >= 3
// static struct PyModuleDef module = {
// PyModuleDef_HEAD_INIT,
// "gomodules",
// NULL,
// -1,
// methods
// };
//
// PyMODINIT_FUNC PyInit_gomodules(void) {
// return PyModule_Create(&module);
// }
// #else
// PyMODINIT_FUNC initgomodules(void) {
// Py_InitModule3("gomodules", methods, NULL);
// }
// #endif
import "C"
15 changes: 15 additions & 0 deletions testing/gomodules/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from setuptools import Extension
from setuptools import setup


setup(
name='gomod',
ext_modules=[Extension('gomodules', ['reversemsg.go'])],
build_golang={
'root': 'github.com/asottile/setuptools-golang/testing/gomodules',
},
# Would do this, but we're testing *our* implementation and this would
# install from pypi. We can rely on setuptools-golang being already
# installed under test.
# setup_requires=['setuptools-golang'],
)
9 changes: 9 additions & 0 deletions tests/setuptools_golang_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ def test_integration_imports_gh(venv):
assert out == '\x1b[31mohai\x1b[0m\n'


GOMOD = 'import gomodules; gomodules.reversemsg()'


def test_integration_gomodules(venv):
run(venv.pip, 'install', os.path.join('testing', 'gomodules'))
out = run_output(venv.python, '-c', GOMOD)
assert out == 'test example'


def test_integration_notfound(venv):
ret = run(
venv.pip, 'install', os.path.join('testing', 'notfound'),
Expand Down

0 comments on commit b9fd68b

Please sign in to comment.