#!/usr/bin/env python3 # -*- coding: ascii -*- """The xonsh installer.""" # Note: Do not embed any non-ASCII characters in this file until pip has been # fixed. See https://github.com/xonsh/xonsh/issues/487. from __future__ import print_function, unicode_literals import os import sys import json import subprocess try: from tempfile import TemporaryDirectory except ImportError: pass try: from setuptools import setup from setuptools.command.sdist import sdist from setuptools.command.install import install from setuptools.command.develop import develop from setuptools.command.install_scripts import install_scripts HAVE_SETUPTOOLS = True except ImportError: from distutils.core import setup from distutils.command.sdist import sdist as sdist from distutils.command.install import install as install from distutils.command.install_scripts import install_scripts HAVE_SETUPTOOLS = False try: from jupyter_client.kernelspec import KernelSpecManager HAVE_JUPYTER = True except ImportError: HAVE_JUPYTER = False TABLES = [ "xonsh/lexer_table.py", "xonsh/parser_table.py", "xonsh/__amalgam__.py", "xonsh/completers/__amalgam__.py", "xonsh/history/__amalgam__.py", "xonsh/prompt/__amalgam__.py", ] def clean_tables(): """Remove the lexer/parser modules that are dynamically created.""" for f in TABLES: if os.path.isfile(f): os.remove(f) print("Removed " + f) os.environ["XONSH_DEBUG"] = "1" from xonsh import __version__ as XONSH_VERSION def amalgamate_source(): """Amalgamates source files.""" sys.path.insert(0, os.path.dirname(__file__)) try: import amalgamate except ImportError: print("Could not import amalgamate, skipping.", file=sys.stderr) return amalgamate.main( [ "amalgamate", "--debug=XONSH_DEBUG", "xonsh", "xonsh.completers", "xonsh.history", "xonsh.prompt", ] ) sys.path.pop(0) def build_tables(): """Build the lexer/parser modules.""" print("Building lexer and parser tables.") sys.path.insert(0, os.path.dirname(__file__)) from xonsh.parser import Parser Parser( lexer_table="lexer_table", yacc_table="parser_table", outputdir="xonsh", yacc_debug=True, ) sys.path.pop(0) def install_jupyter_hook(prefix=None, root=None): """Make xonsh available as a Jupyter kernel.""" if not HAVE_JUPYTER: print( "Could not install Jupyter kernel spec, please install " "Jupyter/IPython." ) return spec = { "argv": [ sys.executable, "-m", "xonsh.jupyter_kernel", "-f", "{connection_file}", ], "display_name": "Xonsh", "language": "xonsh", "codemirror_mode": "shell", } with TemporaryDirectory() as d: os.chmod(d, 0o755) # Starts off as 700, not user readable if sys.platform == "win32": # Ensure that conda-build detects the hard coded prefix spec["argv"][0] = spec["argv"][0].replace(os.sep, os.altsep) with open(os.path.join(d, "kernel.json"), "w") as f: json.dump(spec, f, sort_keys=True) if "CONDA_BUILD" in os.environ: prefix = sys.prefix if sys.platform == "win32": prefix = prefix.replace(os.sep, os.altsep) user = "--user" in sys.argv print("Installing Jupyter kernel spec:") print(" root: {0!r}".format(root)) print(" prefix: {0!r}".format(prefix)) print(" as user: {0}".format(user)) if root and prefix: # os.path.join isn't used since prefix is probably absolute prefix = root + prefix print(" combined prefix {0!r}".format(prefix)) KernelSpecManager().install_kernel_spec( d, "xonsh", user=user, replace=True, prefix=prefix ) def dirty_version(): """ If install/sdist is run from a git directory (not a conda install), add a devN suffix to reported version number and write a gitignored file that holds the git hash of the current state of the repo to be queried by ``xonfig`` """ try: _version = subprocess.check_output(["git", "describe", "--tags"]) except Exception: print("failed to find git tags", file=sys.stderr) return False _version = _version.decode("ascii") try: _, N, sha = _version.strip().split("-") except ValueError: # tag name may contain "-" print("failed to parse git version", file=sys.stderr) return False sha = sha.strip("g") replace_version(N) _cmd = ["git", "show", "-s", "--format=%cd", "--date=local", sha] try: _date = subprocess.check_output(_cmd) _date = _date.decode("ascii") # remove weekday name for a shorter string _date = " ".join(_date.split()[1:]) except: _date = "" print("failed to get commit date", file=sys.stderr) with open("xonsh/dev.githash", "w") as f: f.write("{}|{}".format(sha, _date)) print("wrote git version: " + sha, file=sys.stderr) return True ORIGINAL_VERSION_LINE = None def replace_version(N): """Replace version in `__init__.py` with devN suffix""" global ORIGINAL_VERSION_LINE with open("xonsh/__init__.py", "r") as f: raw = f.read() lines = raw.splitlines() msg_assert = "__version__ must be the first line of the __init__.py" assert "__version__" in lines[0], msg_assert ORIGINAL_VERSION_LINE = lines[0] lines[0] = lines[0].rstrip(' "') + '.dev{}"'.format(N) upd = "\n".join(lines) + "\n" with open("xonsh/__init__.py", "w") as f: f.write(upd) def restore_version(): """If we touch the version in __init__.py discard changes after install.""" if ORIGINAL_VERSION_LINE is None: return with open("xonsh/__init__.py", "r") as f: raw = f.read() lines = raw.splitlines() lines[0] = ORIGINAL_VERSION_LINE upd = "\n".join(lines) + "\n" with open("xonsh/__init__.py", "w") as f: f.write(upd) class xinstall(install): """Xonsh specialization of setuptools install class.""" def run(self): clean_tables() build_tables() amalgamate_source() # add dirty version number dirty = dirty_version() # install Jupyter hook root = self.root if self.root else None prefix = self.prefix if self.prefix else None try: install_jupyter_hook(prefix=prefix, root=root) except Exception: import traceback traceback.print_exc() print("Installing Jupyter hook failed.") super().run() if dirty: restore_version() class xsdist(sdist): """Xonsh specialization of setuptools sdist class.""" def make_release_tree(self, basedir, files): clean_tables() build_tables() amalgamate_source() dirty = dirty_version() super().make_release_tree(basedir, files) if dirty: restore_version() # Hack to overcome pip/setuptools problem on Win 10. See: # https://github.com/tomduck/pandoc-eqnos/issues/6 # https://github.com/pypa/pip/issues/2783 # Custom install_scripts command class for setup() class install_scripts_quoted_shebang(install_scripts): """Ensure there are quotes around shebang paths with spaces.""" def write_script(self, script_name, contents, mode="t", *ignored): shebang = str(contents.splitlines()[0]) if ( shebang.startswith("#!") and " " in shebang[2:].strip() and '"' not in shebang ): quoted_shebang = '#!"%s"' % shebang[2:].strip() contents = contents.replace(shebang, quoted_shebang) super().write_script(script_name, contents, mode, *ignored) class install_scripts_rewrite(install_scripts): """Change default python3 to the concrete python binary used to install/develop inside xon.sh script""" def run(self): super().run() if not self.dry_run: for file in self.get_outputs(): if file.endswith("xon.sh"): # this is the value distutils use on its shebang translation bs_cmd = self.get_finalized_command("build_scripts") exec_param = getattr(bs_cmd, "executable", None) with open(file, "r") as f: content = f.read() processed = content.replace( " python3 ", ' "{}" '.format(exec_param) ) with open(file, "w") as f: f.write(processed) # The custom install needs to be used on Windows machines if os.name == "nt": cmdclass = { "install": xinstall, "sdist": xsdist, "install_scripts": install_scripts_quoted_shebang, } else: cmdclass = { "install": xinstall, "sdist": xsdist, "install_scripts": install_scripts_rewrite, } if HAVE_SETUPTOOLS: class xdevelop(develop): """Xonsh specialization of setuptools develop class.""" def run(self): clean_tables() build_tables() dirty = dirty_version() develop.run(self) if dirty: restore_version() def install_script(self, dist, script_name, script_text, dev_path=None): if script_name == "xon.sh": # change default python3 to the concrete python binary used to install/develop inside xon.sh script script_text = script_text.replace( " python3 ", ' "{}" '.format(sys.executable) ) super().install_script(dist, script_name, script_text, dev_path) def main(): """The main entry point.""" try: if "--name" not in sys.argv: logo_fname = os.path.join(os.path.dirname(__file__), "logo.txt") with open(logo_fname, "rb") as f: logo = f.read().decode("utf-8") print(logo) except UnicodeEncodeError: pass with open(os.path.join(os.path.dirname(__file__), "README.rst"), "r") as f: readme = f.read() scripts = ["scripts/xon.sh"] if sys.platform == "win32": scripts.append("scripts/xonsh.bat") scripts.append("scripts/xonsh-cat.bat") else: scripts.append("scripts/xonsh") scripts.append("scripts/xonsh-cat") skw = dict( name="xonsh", description="Python-powered, cross-platform, Unix-gazing shell", long_description=readme, license="BSD", version=XONSH_VERSION, author="Anthony Scopatz", maintainer="Anthony Scopatz", author_email="scopatz@gmail.com", url="https://github.com/xonsh/xonsh", platforms="Cross Platform", classifiers=["Programming Language :: Python :: 3"], packages=[ "xonsh", "xonsh.ply.ply", "xonsh.ptk", "xonsh.ptk2", "xonsh.parsers", "xonsh.xoreutils", "xontrib", "xonsh.completers", "xonsh.history", "xonsh.prompt", "xonsh.lib", "xonsh.webconfig", ], package_dir={ "xonsh": "xonsh", "xontrib": "xontrib", "xonsh.lib": "xonsh/lib", "xonsh.webconfig": "xonsh/webconfig", }, package_data={ "xonsh": ["*.json", "*.githash"], "xontrib": ["*.xsh"], "xonsh.lib": ["*.xsh"], "xonsh.webconfig": [ "*.html", "js/app.min.js", "js/bootstrap.min.css", "js/LICENSE-bootstrap", ] }, cmdclass=cmdclass, scripts=scripts, ) if HAVE_SETUPTOOLS: # WARNING!!! Do not use setuptools 'console_scripts' # It validates the dependencies (of which we have none) every time the # 'xonsh' command is run. This validation adds ~0.2 sec. to the startup # time of xonsh - for every single xonsh run. This prevents us from # reaching the goal of a startup time of < 0.1 sec. So never ever write # the following: # # 'console_scripts': ['xonsh = xonsh.main:main'], # # END WARNING skw["entry_points"] = { "pygments.lexers": [ "xonsh = xonsh.pyghooks:XonshLexer", "xonshcon = xonsh.pyghooks:XonshConsoleLexer", ], "pytest11": ["xonsh = xonsh.pytest_plugin"], } skw["cmdclass"]["develop"] = xdevelop skw["extras_require"] = { "ptk": ["prompt-toolkit"], "pygments": ["pygments>=2.2"], "mac": ["gnureadline"], "linux": ["distro"], "proctitle": ["setproctitle"], "zipapp": ['importlib_resources; python_version < "3.7"'], } skw["python_requires"] = ">=3.5" setup(**skw) if __name__ == "__main__": main()