Skip to content

Commit cb13cd6

Browse files
authored
Expose the packaging as a plugin hook (#952)
Resolves #951. This will allow having PEP-517/518 for the end users.
1 parent c6112ce commit cb13cd6

File tree

7 files changed

+241
-218
lines changed

7 files changed

+241
-218
lines changed

changelog/951.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
packaging now is exposed as a hook via ``tox_package(session, venv)`` - by :user:`gaborbernat`

src/tox/config.py

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ def get_plugin_manager(plugins=()):
4343
pm.register(tox.interpreters)
4444
pm.register(tox.venv)
4545
pm.register(tox.session)
46+
from tox import package
47+
48+
pm.register(package)
4649
pm.load_setuptools_entrypoints("tox")
4750
for plugin in plugins:
4851
pm.register(plugin)

src/tox/hookspecs.py

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ def tox_configure(config):
1717
"""
1818

1919

20+
@hookspec(firstresult=True)
21+
def tox_package(session, venv):
22+
"""Return the package to be installed for the given venv.
23+
24+
Called once for every environment."""
25+
26+
2027
@hookspec(firstresult=True)
2128
def tox_get_python_executable(envconfig):
2229
"""Return a python executable for the given python base name.

src/tox/package.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import sys
2+
3+
import py
4+
5+
import tox
6+
7+
8+
@tox.hookimpl
9+
def tox_package(session, venv):
10+
"""Build an sdist at first call return that for all calls"""
11+
if not hasattr(session, "package"):
12+
session.package = get_package(session)
13+
return session.package
14+
15+
16+
def get_package(session):
17+
""""Perform the package operation"""
18+
config, report = session.config, session.report
19+
if config.skipsdist:
20+
report.info("skipping sdist step")
21+
return None
22+
if not config.option.sdistonly and (config.sdistsrc or config.option.installpkg):
23+
path = config.option.installpkg
24+
if not path:
25+
path = config.sdistsrc
26+
path = session._resolve_package(path)
27+
report.info("using package {!r}, skipping 'sdist' activity ".format(str(path)))
28+
else:
29+
try:
30+
path = make_sdist(report, config, session)
31+
except tox.exception.InvocationError:
32+
v = sys.exc_info()[1]
33+
report.error("FAIL could not package project - v = {!r}".format(v))
34+
return None
35+
sdist_file = config.distshare.join(path.basename)
36+
if sdist_file != path:
37+
report.info("copying new sdistfile to {!r}".format(str(sdist_file)))
38+
try:
39+
sdist_file.dirpath().ensure(dir=1)
40+
except py.error.Error:
41+
report.warning("could not copy distfile to {}".format(sdist_file.dirpath()))
42+
else:
43+
path.copy(sdist_file)
44+
return path
45+
46+
47+
def make_sdist(report, config, session):
48+
setup = config.setupdir.join("setup.py")
49+
if not setup.check():
50+
report.error(
51+
"No setup.py file found. The expected location is:\n"
52+
" {}\n"
53+
"You can\n"
54+
" 1. Create one:\n"
55+
" https://packaging.python.org/tutorials/distributing-packages/#setup-py\n"
56+
" 2. Configure tox to avoid running sdist:\n"
57+
" http://tox.readthedocs.io/en/latest/example/general.html"
58+
"#avoiding-expensive-sdist".format(setup)
59+
)
60+
raise SystemExit(1)
61+
with session.newaction(None, "packaging") as action:
62+
action.setactivity("sdist-make", setup)
63+
session.make_emptydir(config.distdir)
64+
action.popen(
65+
[sys.executable, setup, "sdist", "--formats=zip", "--dist-dir", config.distdir],
66+
cwd=config.setupdir,
67+
)
68+
try:
69+
return config.distdir.listdir()[0]
70+
except py.error.ENOENT:
71+
# check if empty or comment only
72+
data = []
73+
with open(str(setup)) as fp:
74+
for line in fp:
75+
if line and line[0] == "#":
76+
continue
77+
data.append(line)
78+
if not "".join(data).strip():
79+
report.error("setup.py is empty")
80+
raise SystemExit(1)
81+
report.error(
82+
"No dist directory found. Please check setup.py, e.g with:\n"
83+
" python setup.py sdist"
84+
)
85+
raise SystemExit(1)

src/tox/session.py

+5-85
Original file line numberDiff line numberDiff line change
@@ -431,53 +431,6 @@ def _copyfiles(self, srcdir, pathlist, destdir):
431431
target.dirpath().ensure(dir=1)
432432
src.copy(target)
433433

434-
def _makesdist(self):
435-
setup = self.config.setupdir.join("setup.py")
436-
if not setup.check():
437-
self.report.error(
438-
"No setup.py file found. The expected location is:\n"
439-
" {}\n"
440-
"You can\n"
441-
" 1. Create one:\n"
442-
" https://packaging.python.org/tutorials/distributing-packages/#setup-py\n"
443-
" 2. Configure tox to avoid running sdist:\n"
444-
" http://tox.readthedocs.io/en/latest/example/general.html"
445-
"#avoiding-expensive-sdist".format(setup)
446-
)
447-
raise SystemExit(1)
448-
with self.newaction(None, "packaging") as action:
449-
action.setactivity("sdist-make", setup)
450-
self.make_emptydir(self.config.distdir)
451-
action.popen(
452-
[
453-
sys.executable,
454-
setup,
455-
"sdist",
456-
"--formats=zip",
457-
"--dist-dir",
458-
self.config.distdir,
459-
],
460-
cwd=self.config.setupdir,
461-
)
462-
try:
463-
return self.config.distdir.listdir()[0]
464-
except py.error.ENOENT:
465-
# check if empty or comment only
466-
data = []
467-
with open(str(setup)) as fp:
468-
for line in fp:
469-
if line and line[0] == "#":
470-
continue
471-
data.append(line)
472-
if not "".join(data).strip():
473-
self.report.error("setup.py is empty")
474-
raise SystemExit(1)
475-
self.report.error(
476-
"No dist directory found. Please check setup.py, e.g with:\n"
477-
" python setup.py sdist"
478-
)
479-
raise SystemExit(1)
480-
481434
def make_emptydir(self, path):
482435
if path.check():
483436
self.report.info(" removing {}".format(path))
@@ -564,47 +517,14 @@ def installpkg(self, venv, path):
564517
venv.status = sys.exc_info()[1]
565518
return False
566519

567-
def get_installpkg_path(self):
568-
"""
569-
:return: Path to the distribution
570-
:rtype: py.path.local
571-
"""
572-
if not self.config.option.sdistonly and (
573-
self.config.sdistsrc or self.config.option.installpkg
574-
):
575-
path = self.config.option.installpkg
576-
if not path:
577-
path = self.config.sdistsrc
578-
path = self._resolve_package(path)
579-
self.report.info("using package {!r}, skipping 'sdist' activity ".format(str(path)))
580-
else:
581-
try:
582-
path = self._makesdist()
583-
except tox.exception.InvocationError:
584-
v = sys.exc_info()[1]
585-
self.report.error("FAIL could not package project - v = {!r}".format(v))
586-
return
587-
sdistfile = self.config.distshare.join(path.basename)
588-
if sdistfile != path:
589-
self.report.info("copying new sdistfile to {!r}".format(str(sdistfile)))
590-
try:
591-
sdistfile.dirpath().ensure(dir=1)
592-
except py.error.Error:
593-
self.report.warning(
594-
"could not copy distfile to {}".format(sdistfile.dirpath())
595-
)
596-
else:
597-
path.copy(sdistfile)
598-
return path
599-
600520
def subcommand_test(self):
601521
if self.config.skipsdist:
602522
self.report.info("skipping sdist step")
603-
path = None
604523
else:
605-
path = self.get_installpkg_path()
606-
if not path:
607-
return 2
524+
for venv in self.venvlist:
525+
venv.package = self.hook.tox_package(session=self, venv=venv)
526+
if not venv.package:
527+
return 2
608528
if self.config.option.sdistonly:
609529
return
610530
for venv in self.venvlist:
@@ -617,7 +537,7 @@ def subcommand_test(self):
617537
elif self.config.skipsdist:
618538
self.finishvenv(venv)
619539
else:
620-
self.installpkg(venv, path)
540+
self.installpkg(venv, venv.package)
621541

622542
self.runenvreport(venv)
623543
self.runtestenv(venv)

tests/test_package.py

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import re
2+
3+
from tox.config import parseconfig
4+
from tox.package import get_package
5+
from tox.session import Session
6+
7+
8+
def test_make_sdist(initproj):
9+
initproj(
10+
"example123-0.5",
11+
filedefs={
12+
"tests": {"test_hello.py": "def test_hello(): pass"},
13+
"tox.ini": """
14+
""",
15+
},
16+
)
17+
config = parseconfig([])
18+
session = Session(config)
19+
sdist = get_package(session)
20+
assert sdist.check()
21+
assert sdist.ext == ".zip"
22+
assert sdist == config.distdir.join(sdist.basename)
23+
sdist2 = get_package(session)
24+
assert sdist2 == sdist
25+
sdist.write("hello")
26+
assert sdist.stat().size < 10
27+
sdist_new = get_package(Session(config))
28+
assert sdist_new == sdist
29+
assert sdist_new.stat().size > 10
30+
31+
32+
def test_make_sdist_distshare(tmpdir, initproj):
33+
distshare = tmpdir.join("distshare")
34+
initproj(
35+
"example123-0.6",
36+
filedefs={
37+
"tests": {"test_hello.py": "def test_hello(): pass"},
38+
"tox.ini": """
39+
[tox]
40+
distshare={}
41+
""".format(
42+
distshare
43+
),
44+
},
45+
)
46+
config = parseconfig([])
47+
session = Session(config)
48+
sdist = get_package(session)
49+
assert sdist.check()
50+
assert sdist.ext == ".zip"
51+
assert sdist == config.distdir.join(sdist.basename)
52+
sdist_share = config.distshare.join(sdist.basename)
53+
assert sdist_share.check()
54+
assert sdist_share.read("rb") == sdist.read("rb"), (sdist_share, sdist)
55+
56+
57+
def test_sdistonly(initproj, cmd):
58+
initproj(
59+
"example123",
60+
filedefs={
61+
"tox.ini": """
62+
"""
63+
},
64+
)
65+
result = cmd("-v", "--sdistonly")
66+
assert not result.ret
67+
assert re.match(r".*sdist-make.*setup.py.*", result.out, re.DOTALL)
68+
assert "-mvirtualenv" not in result.out
69+
70+
71+
def test_separate_sdist_no_sdistfile(cmd, initproj, tmpdir):
72+
distshare = tmpdir.join("distshare")
73+
initproj(
74+
("pkg123-foo", "0.7"),
75+
filedefs={
76+
"tox.ini": """
77+
[tox]
78+
distshare={}
79+
""".format(
80+
distshare
81+
)
82+
},
83+
)
84+
result = cmd("--sdistonly")
85+
assert not result.ret
86+
distshare_files = distshare.listdir()
87+
assert len(distshare_files) == 1
88+
sdistfile = distshare_files[0]
89+
assert "pkg123-foo-0.7.zip" in str(sdistfile)
90+
91+
92+
def test_separate_sdist(cmd, initproj, tmpdir):
93+
distshare = tmpdir.join("distshare")
94+
initproj(
95+
"pkg123-0.7",
96+
filedefs={
97+
"tox.ini": """
98+
[tox]
99+
distshare={}
100+
sdistsrc={{distshare}}/pkg123-0.7.zip
101+
""".format(
102+
distshare
103+
)
104+
},
105+
)
106+
result = cmd("--sdistonly")
107+
assert not result.ret
108+
sdistfiles = distshare.listdir()
109+
assert len(sdistfiles) == 1
110+
sdistfile = sdistfiles[0]
111+
result = cmd("-v", "--notest")
112+
assert not result.ret
113+
assert "python inst: {}".format(sdistfile) in result.out
114+
115+
116+
def test_sdist_latest(tmpdir, newconfig):
117+
distshare = tmpdir.join("distshare")
118+
config = newconfig(
119+
[],
120+
"""
121+
[tox]
122+
distshare={}
123+
sdistsrc={{distshare}}/pkg123-*
124+
""".format(
125+
distshare
126+
),
127+
)
128+
p = distshare.ensure("pkg123-1.4.5.zip")
129+
distshare.ensure("pkg123-1.4.5a1.zip")
130+
session = Session(config)
131+
sdist_path = get_package(session)
132+
assert sdist_path == p
133+
134+
135+
def test_installpkg(tmpdir, newconfig):
136+
p = tmpdir.ensure("pkg123-1.0.zip")
137+
config = newconfig(["--installpkg={}".format(p)], "")
138+
session = Session(config)
139+
sdist_path = get_package(session)
140+
assert sdist_path == p

0 commit comments

Comments
 (0)