mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 10:04:07 +00:00
328 lines
9.5 KiB
Python
Executable File
328 lines
9.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) Jupyter Development Team.
|
|
# Distributed under the terms of the Modified BSD License.
|
|
# -----------------------------------------------------------------------------
|
|
# Minimal Python version sanity check (from IPython)
|
|
# -----------------------------------------------------------------------------
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from subprocess import check_call
|
|
|
|
from setuptools import Command
|
|
from setuptools import setup
|
|
from setuptools.command.bdist_egg import bdist_egg
|
|
from setuptools.command.build_py import build_py
|
|
from setuptools.command.sdist import sdist
|
|
|
|
|
|
v = sys.version_info
|
|
if v[:2] < (3, 6):
|
|
error = "ERROR: JupyterHub requires Python version 3.6 or above."
|
|
print(error, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
shell = False
|
|
if os.name in ('nt', 'dos'):
|
|
shell = True
|
|
warning = "WARNING: Windows is not officially supported"
|
|
print(warning, file=sys.stderr)
|
|
|
|
|
|
pjoin = os.path.join
|
|
|
|
here = os.path.abspath(os.path.dirname(__file__))
|
|
share_jupyterhub = pjoin(here, 'share', 'jupyterhub')
|
|
static = pjoin(share_jupyterhub, 'static')
|
|
|
|
is_repo = os.path.exists(pjoin(here, '.git'))
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Build basic package data, etc.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def get_data_files():
|
|
"""Get data files in share/jupyter"""
|
|
|
|
data_files = []
|
|
for (d, dirs, filenames) in os.walk(share_jupyterhub):
|
|
rel_d = os.path.relpath(d, here)
|
|
data_files.append((rel_d, [os.path.join(rel_d, f) for f in filenames]))
|
|
return data_files
|
|
|
|
|
|
def get_package_data():
|
|
"""Get package data
|
|
|
|
(mostly alembic config)
|
|
"""
|
|
package_data = {}
|
|
package_data['jupyterhub'] = ['alembic.ini', 'alembic/*', 'alembic/versions/*']
|
|
return package_data
|
|
|
|
|
|
ns = {}
|
|
with open(pjoin(here, 'jupyterhub', '_version.py')) as f:
|
|
exec(f.read(), {}, ns)
|
|
|
|
|
|
packages = []
|
|
for d, _, _ in os.walk('jupyterhub'):
|
|
if os.path.exists(pjoin(d, '__init__.py')):
|
|
packages.append(d.replace(os.path.sep, '.'))
|
|
|
|
with open('README.md', encoding="utf8") as f:
|
|
readme = f.read()
|
|
|
|
|
|
setup_args = dict(
|
|
name='jupyterhub',
|
|
packages=packages,
|
|
# dummy, so that install_data doesn't get skipped
|
|
# this will be overridden when bower is run anyway
|
|
data_files=get_data_files() or ['dummy'],
|
|
package_data=get_package_data(),
|
|
version=ns['__version__'],
|
|
description="JupyterHub: A multi-user server for Jupyter notebooks",
|
|
long_description=readme,
|
|
long_description_content_type='text/markdown',
|
|
author="Jupyter Development Team",
|
|
author_email="jupyter@googlegroups.com",
|
|
url="https://jupyter.org",
|
|
license="BSD",
|
|
platforms="Linux, Mac OS X",
|
|
keywords=['Interactive', 'Interpreter', 'Shell', 'Web'],
|
|
python_requires=">=3.6",
|
|
entry_points={
|
|
'jupyterhub.authenticators': [
|
|
'default = jupyterhub.auth:PAMAuthenticator',
|
|
'pam = jupyterhub.auth:PAMAuthenticator',
|
|
'dummy = jupyterhub.auth:DummyAuthenticator',
|
|
'null = jupyterhub.auth:NullAuthenticator',
|
|
],
|
|
'jupyterhub.proxies': [
|
|
'default = jupyterhub.proxy:ConfigurableHTTPProxy',
|
|
'configurable-http-proxy = jupyterhub.proxy:ConfigurableHTTPProxy',
|
|
],
|
|
'jupyterhub.spawners': [
|
|
'default = jupyterhub.spawner:LocalProcessSpawner',
|
|
'localprocess = jupyterhub.spawner:LocalProcessSpawner',
|
|
'simple = jupyterhub.spawner:SimpleLocalProcessSpawner',
|
|
],
|
|
'console_scripts': [
|
|
'jupyterhub = jupyterhub.app:main',
|
|
'jupyterhub-singleuser = jupyterhub.singleuser:main',
|
|
],
|
|
},
|
|
classifiers=[
|
|
'Intended Audience :: Developers',
|
|
'Intended Audience :: System Administrators',
|
|
'Intended Audience :: Science/Research',
|
|
'License :: OSI Approved :: BSD License',
|
|
'Programming Language :: Python',
|
|
'Programming Language :: Python :: 3',
|
|
],
|
|
project_urls={
|
|
'Documentation': 'https://jupyterhub.readthedocs.io',
|
|
'Funding': 'https://jupyter.org/about',
|
|
'Source': 'https://github.com/jupyterhub/jupyterhub/',
|
|
'Tracker': 'https://github.com/jupyterhub/jupyterhub/issues',
|
|
},
|
|
)
|
|
|
|
|
|
def mtime(path):
|
|
"""shorthand for mtime"""
|
|
return os.stat(path).st_mtime
|
|
|
|
|
|
class BaseCommand(Command):
|
|
"""Dumb empty command because Command needs subclasses to override too much"""
|
|
|
|
user_options = []
|
|
|
|
def initialize_options(self):
|
|
pass
|
|
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def get_inputs(self):
|
|
return []
|
|
|
|
def get_outputs(self):
|
|
return []
|
|
|
|
|
|
class NPM(BaseCommand):
|
|
description = "fetch static client-side components with bower"
|
|
|
|
user_options = []
|
|
node_modules = pjoin(here, 'node_modules')
|
|
bower_dir = pjoin(static, 'components')
|
|
|
|
def should_run(self):
|
|
if not shutil.which('npm'):
|
|
print("npm unavailable", file=sys.stderr)
|
|
return False
|
|
if not os.path.exists(self.bower_dir):
|
|
return True
|
|
if not os.path.exists(self.node_modules):
|
|
return True
|
|
if mtime(self.bower_dir) < mtime(self.node_modules):
|
|
return True
|
|
return mtime(self.node_modules) < mtime(pjoin(here, 'package.json'))
|
|
|
|
def run(self):
|
|
if not self.should_run():
|
|
print("npm dependencies up to date")
|
|
return
|
|
|
|
print("installing js dependencies with npm")
|
|
check_call(
|
|
['npm', 'install', '--progress=false', '--unsafe-perm'],
|
|
cwd=here,
|
|
shell=shell,
|
|
)
|
|
os.utime(self.node_modules)
|
|
|
|
os.utime(self.bower_dir)
|
|
# update data-files in case this created new files
|
|
self.distribution.data_files = get_data_files()
|
|
|
|
|
|
class CSS(BaseCommand):
|
|
description = "compile CSS from LESS"
|
|
|
|
def should_run(self):
|
|
"""Does less need to run?"""
|
|
# from IPython.html.tasks.py
|
|
|
|
css_targets = [pjoin(static, 'css', 'style.min.css')]
|
|
css_maps = [t + '.map' for t in css_targets]
|
|
targets = css_targets + css_maps
|
|
if not all(os.path.exists(t) for t in targets):
|
|
# some generated files don't exist
|
|
return True
|
|
earliest_target = sorted(mtime(t) for t in targets)[0]
|
|
|
|
# check if any .less files are newer than the generated targets
|
|
for (dirpath, dirnames, filenames) in os.walk(static):
|
|
for f in filenames:
|
|
if f.endswith('.less'):
|
|
path = pjoin(static, dirpath, f)
|
|
timestamp = mtime(path)
|
|
if timestamp > earliest_target:
|
|
return True
|
|
|
|
return False
|
|
|
|
def run(self):
|
|
if not self.should_run():
|
|
print("CSS up-to-date")
|
|
return
|
|
|
|
self.run_command('js')
|
|
print("Building css with less")
|
|
|
|
style_less = pjoin(static, 'less', 'style.less')
|
|
style_css = pjoin(static, 'css', 'style.min.css')
|
|
sourcemap = style_css + '.map'
|
|
|
|
args = [
|
|
'npm',
|
|
'run',
|
|
'lessc',
|
|
'--',
|
|
'--clean-css',
|
|
f'--source-map-basepath={static}',
|
|
f'--source-map={sourcemap}',
|
|
'--source-map-rootpath=../',
|
|
style_less,
|
|
style_css,
|
|
]
|
|
try:
|
|
check_call(args, cwd=here, shell=shell)
|
|
except OSError as e:
|
|
print("Failed to run lessc: %s" % e, file=sys.stderr)
|
|
print("You can install js dependencies with `npm install`", file=sys.stderr)
|
|
raise
|
|
# update data-files in case this created new files
|
|
self.distribution.data_files = get_data_files()
|
|
|
|
|
|
def js_css_first(cls, strict=True):
|
|
class Command(cls):
|
|
def run(self):
|
|
try:
|
|
self.run_command('js')
|
|
self.run_command('css')
|
|
except Exception:
|
|
if strict:
|
|
raise
|
|
else:
|
|
pass
|
|
return super().run()
|
|
|
|
return Command
|
|
|
|
|
|
class bdist_egg_disabled(bdist_egg):
|
|
"""Disabled version of bdist_egg
|
|
|
|
Prevents setup.py install from performing setuptools' default easy_install,
|
|
which it should never ever do.
|
|
"""
|
|
|
|
def run(self):
|
|
sys.exit(
|
|
"Aborting implicit building of eggs. Use `pip install .` to install from source."
|
|
)
|
|
|
|
|
|
setup_args['cmdclass'] = {
|
|
'js': NPM,
|
|
'css': CSS,
|
|
'build_py': js_css_first(build_py, strict=is_repo),
|
|
'sdist': js_css_first(sdist, strict=True),
|
|
'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled,
|
|
}
|
|
|
|
|
|
# setuptools requirements
|
|
|
|
setup_args['zip_safe'] = False
|
|
from setuptools.command.develop import develop
|
|
|
|
|
|
class develop_js_css(develop):
|
|
def run(self):
|
|
if not self.uninstall:
|
|
self.distribution.run_command('js')
|
|
self.distribution.run_command('css')
|
|
super().run()
|
|
|
|
|
|
setup_args['cmdclass']['develop'] = develop_js_css
|
|
setup_args['install_requires'] = install_requires = []
|
|
|
|
with open('requirements.txt') as f:
|
|
for line in f.readlines():
|
|
req = line.strip()
|
|
if not req or req.startswith('#') or '://' in req:
|
|
continue
|
|
install_requires.append(req)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# setup
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def main():
|
|
setup(**setup_args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|