Skip to content

Commit

Permalink
Included the --build hook, with import adjustments
Browse files Browse the repository at this point in the history
- As the build hook directly invokes the module code as a module before
  it is properly installed, a number of adjustments to imports and the
  creation of helpers must be done, such as delaying the import of ply
  and providing a fake helper that will defer the import error in the
  off chance that the module was invoked in such a manner without all
  the dependencies in place.
- The --build hook should force the generation of the intended generated
  modules to ensure that the files are available for the setup.py build
  step to ensure their persistence into the egg-info metadata file along
  with its presence in the build step, which turns out to be a rather
  important step for distros.
- As a consequnce, the post install generation hook can be removed (not
  to mention it can fail in various cases, such as whenever there are
  manual paths being specified to accommodate alternative build/install
  workflows).
- Also cleaned up the optimize module method for the other Python 2
  import case, where previously the manual optimize step may not result
  in the full cleanup before the regeneration step.
- README entries referenced to be updated (being reworded).
  • Loading branch information
metatoaster committed Jul 2, 2022
1 parent 3a8d4ec commit 4d07244
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 26 deletions.
17 changes: 5 additions & 12 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
import atexit
import sys
from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.build_py import build_py
from subprocess import call


class BuildHook(build_py):
"""Forcing the optimizer to run before the build step"""
def __init__(self, *a, **kw):
call([sys.executable, '-m', 'calmjs.parse.parsers.optimize'], env={
code = call([
sys.executable, '-m', 'calmjs.parse.parsers.optimize', '--build'
], env={
'PYTHONPATH': 'src'
})
if code:
sys.exit(1)
build_py.__init__(self, *a, **kw)


class InstallHook(install):
"""For hooking the optimizer when setup exits"""
def __init__(self, *a, **kw):
install.__init__(self, *a, **kw)
atexit.register(
call, [sys.executable, '-m', 'calmjs.parse.parsers.optimize'])


# Attributes

version = '1.3.1'
Expand Down Expand Up @@ -69,7 +63,6 @@ def __init__(self, *a, **kw):
include_package_data=True,
zip_safe=False,
cmdclass={
'install': InstallHook,
'build_py': BuildHook,
},
install_requires=[
Expand Down
11 changes: 9 additions & 2 deletions src/calmjs/parse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
Quick access helper functions
"""

from calmjs.parse.factory import ParserUnparserFactory
try:
from calmjs.parse.factory import ParserUnparserFactory
except ImportError as e: # pragma: no cover
exc = e

def import_error(*a, **kw):
raise exc

es5 = ParserUnparserFactory('es5', 'pretty_print', 'minify_print')
es5 = import_error
else:
es5 = ParserUnparserFactory('es5', 'pretty_print', 'minify_print')
41 changes: 30 additions & 11 deletions src/calmjs/parse/parsers/optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@
from functools import partial
from os import unlink
from os.path import exists
from ply import lex
from importlib import import_module

# have to do this for every parser modules
from calmjs.parse.parsers import es5


def find_tab_paths(module):
# return a list of lextab/yacctab module paths and a list of missing
Expand Down Expand Up @@ -52,10 +48,15 @@ def verify_paths(paths):
for path in paths:
if exists(path):
yield path
# locate any adjacent .py[co]? files based on module path
# returned; mostly a problem with Python 2
if path[-4:] in ('.pyc', '.pyo'):
# find the .py file, too.
if exists(path[:-1]):
yield path[:-1]
else:
for c in 'co':
if exists(path + c):
yield path + c


def unlink_modules(paths):
Expand Down Expand Up @@ -98,13 +99,31 @@ def reoptimize_all(monkey_patch=False, first_build=False):
"""

if monkey_patch:
lex.open = partial(codecs.open, encoding='utf8')
modules = (es5,)
for module in modules:
if first_build:
optimize_build(module)
try:
from ply import lex
except ImportError: # pragma: no cover
pass # fail later; only fail if import ply is truly needed
else:
reoptimize(module)
lex.open = partial(codecs.open, encoding='utf8')

modules = ('.es5',)
try:
for name in modules:
module = import_module(name, 'calmjs.parse.parsers')
if first_build:
optimize_build(module)
else:
reoptimize(module)
except ImportError as e:
if not first_build or 'ply' not in str(e):
raise
sys.stderr.write(
u"ERROR: cannot import ply, aborting build; please ensure "
"that the python package 'ply' is installed before attempting to "
"build this package; please refer to the top level README for "
"further details\n"
)
sys.exit(1)


if __name__ == '__main__': # pragma: no cover
Expand Down
41 changes: 40 additions & 1 deletion src/calmjs/parse/tests/test_parsers_optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import sys

from io import StringIO
from shutil import rmtree
from tempfile import mkdtemp
from types import ModuleType
Expand All @@ -25,13 +26,22 @@ def tearDown(self):
# undo whatever monkey patch that may have happened
lex.open = open

def break_ply(self):
import ply

def cleanup():
sys.modules['ply'] = ply

self.addCleanup(cleanup)
sys.modules['ply'] = None

def test_verify_paths(self):
tempdir = mkdtemp()
self.addCleanup(rmtree, tempdir)

# create fake module files
modules = [os.path.join(tempdir, name) for name in (
'foo.pyc', 'bar.pyc', 'foo.py')]
'foo.pyc', 'bar.py', 'bar.pyc', 'foo.py')]

for module in modules:
with open(module, 'w'):
Expand Down Expand Up @@ -125,3 +135,32 @@ def test_optimize_first_build(self):
optimize.reoptimize_all(True, first_build=True)
# shouldn't have purged any modules
self.assertEqual(len(self.purged), 0)

def test_optimize_first_build_valid_with_broken_ply(self):
self.break_ply()
optimize.reoptimize_all(True, first_build=True)
# shouldn't have purged any modules
self.assertEqual(len(self.purged), 0)

def test_optimize_first_build_valid_with_broken_ply_error(self):
def fail_import(module, package):
raise ImportError('no module named ply')

optimize.import_module = fail_import

with self.assertRaises(ImportError):
optimize.reoptimize_all()

stderr = sys.stderr

def cleanup():
sys.stderr = stderr

self.addCleanup(cleanup)

sys.stderr = StringIO()
with self.assertRaises(SystemExit):
optimize.reoptimize_all(first_build=True)

self.assertTrue(sys.stderr.getvalue().startswith(
'ERROR: cannot import ply'))

0 comments on commit 4d07244

Please sign in to comment.