Skip to content

Commit

Permalink
enaml: setuptools cmd to auto-install and byte-compile enaml files
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthieuDartiailh committed Jun 29, 2017
1 parent cbaec23 commit 9a45e45
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 15 deletions.
150 changes: 150 additions & 0 deletions enaml/build_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#------------------------------------------------------------------------------
# Copyright (c) 2013-2017, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
"""Tools to make building project including enaml files easier.
To use those in you project you should:
- declare the following two functions
def enaml_build_py(*args, **kwargs):
from enaml.build_tools import EnamlBuildPy
return EnamlBuildPy(*args, **kwargs)
def enaml_install_lib(*args, **kwargs):
from enaml.build_tools import EnamlInstallLib
return EnamlInstallLib(*args, **kwargs)
- specify enaml as a setup_requires in your setup function and register the
custom command classes as follow:
setup(name='myproject',
setup_requires=['enaml'],
cmdclass={'build_py': enaml_build_py,
'install_lib': enaml_install_lib})
"""
import os
import sys
import itertools
from glob import glob
from distutils import log

from setuptools.command.install_lib import install_lib
from setuptools.command.build_py import build_py

from .core.import_hooks import make_file_info, EnamlImporter


def enaml_byte_compile(files):
"""Byte compile enaml files in a list of files.
"""
for file in files:
if not file.endswith('.enaml'):
continue
log.info("byte-compiling %s", file)
file_info = make_file_info(file)
EnamlImporter(file_info).compile_code()


class EnamlBuildPy(build_py):
"""Build command including enaml files and byte compiling them.
To use it simply import in your setup.py and add the following line to the
arguments of setup
cmdclass={'build_py': BuildPyAndEnaml}
"""

def __getattr__(self, attr):
if attr == 'enaml_files':
self.enaml_files = self._get_enaml_files()
return self.enaml_files
return build_py.__getattr__(self, attr)

def _get_data_files(self):
data_files = super(EnamlBuildPy, self)._get_data_files()
return data_files + self.enaml_files

def _get_enaml_files(self):
"""Generate list of '(package,src_dir,build_dir,filenames)' tuples for
enaml files.
"""
self.analyze_manifest()
return list(map(self._get_pkg_enaml_files, self.packages or ()))

def _get_pkg_enaml_files(self, package):
# Locate package source directory
src_dir = self.get_package_dir(package)

# Compute package build directory
build_dir = os.path.join(*([self.build_lib] + package.split('.')))

# Strip directory from globbed filenames
filenames = [
os.path.relpath(file, src_dir)
for file in self.find_enaml_files(package, src_dir)
]
return package, src_dir, build_dir, filenames

def find_enaml_files(self, package, src_dir):
"""Return filenames for package's data files in 'src_dir'
"""
patterns = self._get_platform_patterns(
{'': ['*.enaml']},
package,
src_dir,
)
globs_expanded = map(glob, patterns)
# flatten the expanded globs into an iterable of matches
globs_matches = itertools.chain.from_iterable(globs_expanded)
glob_files = filter(os.path.isfile, globs_matches)
files = itertools.chain(
self.manifest_files.get(package, []),
glob_files,
)
return self.exclude_data_files(package, src_dir, files)

def byte_compile(self, files):
"""Byte compile enaml files and write the cache files.
"""
super(EnamlBuildPy, self).byte_compile(files)
if sys.dont_write_bytecode:
self.warn('byte-compiling is disabled, skipping.')
return

if self.dry_run:
return

if self.compile or self.optimize > 0:
enaml_byte_compile(files)


class EnamlInstallLib(install_lib):
"""Compile enaml files when installing a lib.
"""

def byte_compile(self, files):
"""Byte compile enaml files and write the cache files.
"""
super(EnamlInstallLib, self).byte_compile(files)
if sys.dont_write_bytecode:
self.warn('byte-compiling is disabled, skipping.')
return

if self.dry_run:
return

if self.compile or self.optimize > 0:
enaml_byte_compile(files)
30 changes: 23 additions & 7 deletions enaml/core/import_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,28 @@ def _get_magic_info(self, file_info):
timestamp = struct.unpack('i', cache_file.read(4))[0]
return (magic, timestamp)

def compile_code(self):
""" Compile the code object for the Enaml module and
the full path to the module for use as the __file__ attribute
of the module.
Returns
-------
result : (code, path)
The Python code object for the .enaml module, and the full
path to the module as a string.
"""
file_info = self.file_info
src_mod_time = int(os.path.getmtime(file_info.src_path))
encoding = detect_encoding(file_info.src_path)
with open_source(file_info.src_path) as src_file:
src = src_file.read()
ast = parse(src, encoding)
code = EnamlCompiler.compile(ast, file_info.src_path)
self._write_cache(code, src_mod_time, file_info)
return (code, file_info.src_path)

def get_code(self):
""" Loads and returns the code object for the Enaml module and
the full path to the module for use as the __file__ attribute
Expand Down Expand Up @@ -364,13 +386,7 @@ def get_code(self):
return (code, file_info.src_path)

# Otherwise, compile from source and attempt to cache
encoding = detect_encoding(file_info.src_path)
with open_source(file_info.src_path) as src_file:
src = src_file.read()
ast = parse(src, encoding)
code = EnamlCompiler.compile(ast, file_info.src_path)
self._write_cache(code, src_mod_time, file_info)
return (code, file_info.src_path)
return self.compile_code()


#------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion enaml/core/parsing/base_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3225,7 +3225,7 @@ def p_varargslist2(self, p):
''' varargslist : fpdef COMMA STAR fpdef COMMA DOUBLESTAR fpdef '''
# def f(a, *args, **kwargs): pass
# def f((a, b), *args, **kwargs): pass
p[0] = self._make_args([p[1]], vararg=p[4], kwargs=p[7])
p[0] = self._make_args([p[1]], vararg=p[4], kwarg=p[7])

def p_varargslist3(self, p):
''' varargslist : fpdef COMMA DOUBLESTAR fpdef '''
Expand Down
15 changes: 8 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
#------------------------------------------------------------------------------
# Copyright (c) 2013, Nucleic Development Team.
# Copyright (c) 2013-2017, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
import os
import sys
from setuptools import setup, find_packages, Extension
from setuptools import find_packages, Extension, setup
from setuptools.command.build_ext import build_ext
from setuptools.command.install import install
from setuptools.command.develop import develop

sys.path.insert(0, os.path.abspath('.'))
from enaml.build_tools import EnamlBuildPy, EnamlInstallLib

ext_modules = [
Extension(
Expand Down Expand Up @@ -129,10 +132,6 @@ def run(self):
'kiwisolver >= 0.2.0.dev', 'ply >= 3.4', 'qtpy'],
packages=find_packages(),
package_data={
'enaml.applib': ['*.enaml'],
'enaml.stdlib': ['*.enaml'],
'enaml.workbench.core': ['*.enaml'],
'enaml.workbench.ui': ['*.enaml'],
'enaml.qt.docking': [
'dock_images/*.png',
'dock_images/*.py',
Expand All @@ -142,6 +141,8 @@ def run(self):
entry_points={'console_scripts': ['enaml-run = enaml.runner:main']},
ext_modules=ext_modules,
cmdclass={'build_ext': BuildExt,
'build_py': EnamlBuildPy,
'install': Install,
'develop': Develop},
'develop': Develop,
'install_lib': EnamlInstallLib},
)

0 comments on commit 9a45e45

Please sign in to comment.