kopia lustrzana https://github.com/jupyterhub/repo2docker
Merge pull request #595 from davidanthoff/reorg-julia
[MRG+1] Split julia support into pre julia 1.0 and post julia 1.0pull/614/head
commit
a9dd69a3ce
|
@ -9,6 +9,8 @@ Release date: TBD
|
|||
|
||||
New features
|
||||
------------
|
||||
- Support for julia `Project.toml`, `JuliaProject.toml` and `Manifest.toml` files in :pr:`595` by
|
||||
:user:`davidanthoff`
|
||||
|
||||
API changes
|
||||
-----------
|
||||
|
|
|
@ -71,15 +71,30 @@ To install your repository like a Python package, you may include a
|
|||
``setup.py`` file. repo2docker installs ``setup.py`` files by running
|
||||
``pip install -e .``.
|
||||
|
||||
.. _Project.toml:
|
||||
|
||||
``Project.toml`` - Install a Julia environment
|
||||
==============================================
|
||||
|
||||
A ``Project.toml`` (or ``JuliaProject.toml``) file can specify both the
|
||||
version of Julia to be used and a list of Julia packages to be installed.
|
||||
If a ``Manifest.toml`` is present, it will determine the exact versions
|
||||
of the Julia packages that are installed.
|
||||
|
||||
|
||||
.. _REQUIRE:
|
||||
|
||||
``REQUIRE`` - Install a Julia environment
|
||||
=========================================
|
||||
``REQUIRE`` - Install a Julia environment (legacy)
|
||||
==================================================
|
||||
|
||||
This specifies a list of Julia packages. To see an example of a
|
||||
Julia repository with ``REQUIRE`` and ``environment.yml``,
|
||||
visit `binder-examples/julia-python <https://github.com/binder-examples/julia-python>`_.
|
||||
A ``REQUIRE`` file can specify both the version of Julia to be used and
|
||||
which Julia packages should be used. The use of ``REQUIRE`` is only
|
||||
recommended for pre 1.0 Julia versions. The recommended way of installing
|
||||
a Julia environment that uses Julia 1.0 or newer is to use a ``Project.toml``
|
||||
file. If both a ``REQUIRE`` and a ``Project.toml`` file are detected,
|
||||
the ``REQUIRE`` file is ignored. To see an example of a Julia repository
|
||||
with ``REQUIRE`` and ``environment.yml``, visit
|
||||
`binder-examples/julia-python <https://github.com/binder-examples/julia-python>`_.
|
||||
|
||||
|
||||
.. _install.R:
|
||||
|
@ -186,7 +201,7 @@ For these cases, we have a special file, ``runtime.txt``.
|
|||
|
||||
``runtime.txt`` is only supported when used with environment specifications
|
||||
that do not already support specifying the runtime
|
||||
(e.g. when using ``environment.yml`` for conda or ``REQUIRE`` for Julia,
|
||||
(e.g. when using ``environment.yml`` for conda or ``Project.toml`` for Julia,
|
||||
``runtime.txt`` will be ignored).
|
||||
|
||||
To use python-2.7: add ``python-2.7`` in runtime.txt file.
|
||||
|
|
|
@ -42,12 +42,9 @@ environment.
|
|||
Julia
|
||||
~~~~~
|
||||
|
||||
The following versions of Julia are supported (specified in the
|
||||
:ref:`REQUIRE <REQUIRE>` configuration file):
|
||||
|
||||
- 1.0 (added in 0.7)
|
||||
- 0.7 (added in 0.7)
|
||||
- 0.6 (default)
|
||||
All Julia versions since Julia 0.7.0 are supported via a :ref:`Project.toml <Project.toml>`
|
||||
file, and this is the recommended way to install Julia environments.
|
||||
Julia versions 0.6.x and earlier are supported via a :ref:`REQUIRE <REQUIRE>` file.
|
||||
|
||||
R
|
||||
~
|
||||
|
|
|
@ -72,11 +72,11 @@ Julia
|
|||
=====
|
||||
|
||||
To build an environment with Julia, include a configuration file called
|
||||
``REQUIRE``. Each line of this file should include a package that you wish
|
||||
to have installed with Julia. For example, the following contents of ``REQURE``
|
||||
would install the ``PyPlot`` package with your Julia environment.::
|
||||
|
||||
PyPlot
|
||||
``Project.toml``. The format of this file is documented at
|
||||
`the Julia Pkg.jl documentation <https://julialang.github.io/Pkg.jl/stable/>`_.
|
||||
To specify a specific version of Julia to install, put a Julia version in the
|
||||
``Compat`` section of the ``Project.toml`` file, as described
|
||||
here: https://julialang.github.io/Pkg.jl/stable/compatibility.
|
||||
|
||||
Languages not covered here
|
||||
==========================
|
||||
|
|
|
@ -30,7 +30,7 @@ from traitlets.config import Application
|
|||
from . import __version__
|
||||
from .buildpacks import (
|
||||
PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack,
|
||||
CondaBuildPack, JuliaBuildPack, RBuildPack, NixBuildPack
|
||||
CondaBuildPack, JuliaProjectTomlBuildPack, JuliaRequireBuildPack, RBuildPack, NixBuildPack
|
||||
)
|
||||
from . import contentproviders
|
||||
from .utils import ByteSpecification, chdir
|
||||
|
@ -85,7 +85,8 @@ class Repo2Docker(Application):
|
|||
[
|
||||
LegacyBinderDockerBuildPack,
|
||||
DockerBuildPack,
|
||||
JuliaBuildPack,
|
||||
JuliaProjectTomlBuildPack,
|
||||
JuliaRequireBuildPack,
|
||||
NixBuildPack,
|
||||
RBuildPack,
|
||||
CondaBuildPack,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from .base import BuildPack, BaseImage
|
||||
from .python import PythonBuildPack
|
||||
from .conda import CondaBuildPack
|
||||
from .julia import JuliaBuildPack
|
||||
from .julia import JuliaProjectTomlBuildPack
|
||||
from .julia import JuliaRequireBuildPack
|
||||
from .docker import DockerBuildPack
|
||||
from .legacy import LegacyBinderDockerBuildPack
|
||||
from .r import RBuildPack
|
||||
|
|
|
@ -1,170 +1,2 @@
|
|||
"""Generates a Dockerfile based on an input matrix for Julia"""
|
||||
import os
|
||||
from ..python import PythonBuildPack
|
||||
|
||||
|
||||
class JuliaBuildPack(PythonBuildPack):
|
||||
"""
|
||||
Julia build pack which uses conda.
|
||||
"""
|
||||
|
||||
minor_julias = {
|
||||
'0.6': '0.6.4',
|
||||
'0.7': '0.7.0',
|
||||
'1.0': '1.0.3',
|
||||
'1.1': '1.1.0',
|
||||
}
|
||||
major_julias = {
|
||||
'1': '1.1.0',
|
||||
}
|
||||
|
||||
@property
|
||||
def julia_version(self):
|
||||
require = self.binder_path('REQUIRE')
|
||||
try:
|
||||
with open(require) as f:
|
||||
julia_version_line = f.readline().strip() # First line is optionally a julia version
|
||||
except FileNotFoundError:
|
||||
julia_version_line = ''
|
||||
|
||||
if not julia_version_line.startswith('julia '):
|
||||
# not a Julia version line.
|
||||
# use the default Julia.
|
||||
self._julia_version = self.minor_julias['0.6']
|
||||
return self._julia_version
|
||||
|
||||
julia_version_info = julia_version_line.split(' ', 1)[1].split('.')
|
||||
julia_version = ''
|
||||
if len(julia_version_info) == 1:
|
||||
julia_version = self.major_julias[julia_version_info[0]]
|
||||
elif len(julia_version_info) == 2:
|
||||
# get major.minor
|
||||
julia_version = self.minor_julias['.'.join(julia_version_info)]
|
||||
else:
|
||||
# use supplied julia version
|
||||
julia_version = '.'.join(julia_version_info)
|
||||
self._julia_version = julia_version
|
||||
return self._julia_version
|
||||
|
||||
def get_build_env(self):
|
||||
"""Get additional environment settings for Julia and Jupyter
|
||||
|
||||
Returns:
|
||||
an ordered list of environment setting tuples
|
||||
|
||||
The tuples contain a string of the environment variable name and
|
||||
a string of the environment setting:
|
||||
- `JULIA_PATH`: base path where all Julia Binaries and libraries
|
||||
will be installed
|
||||
- `JULIA_HOME`: path where all Julia Binaries will be installed
|
||||
- `JULIA_PKGDIR`: path where all Julia libraries will be installed
|
||||
- `JULIA_DEPOT_PATH`: path where Julia libraries are installed.
|
||||
Similar to JULIA_PKGDIR, used in 1.x.
|
||||
- `JULIA_VERSION`: default version of julia to be installed
|
||||
- `JUPYTER`: environment variable required by IJulia to point to
|
||||
the `jupyter` executable
|
||||
|
||||
For example, a tuple may be `('JULIA_VERSION', '0.6.0')`.
|
||||
|
||||
"""
|
||||
return super().get_build_env() + [
|
||||
('JULIA_PATH', '${APP_BASE}/julia'),
|
||||
('JULIA_HOME', '${JULIA_PATH}/bin'), # julia <= 0.6
|
||||
('JULIA_BINDIR', '${JULIA_HOME}'), # julia >= 0.7
|
||||
('JULIA_PKGDIR', '${JULIA_PATH}/pkg'),
|
||||
('JULIA_DEPOT_PATH', '${JULIA_PKGDIR}'), # julia >= 0.7
|
||||
('JULIA_VERSION', self.julia_version),
|
||||
('JUPYTER', '${NB_PYTHON_PREFIX}/bin/jupyter')
|
||||
]
|
||||
|
||||
def get_path(self):
|
||||
"""Adds path to Julia binaries to user's PATH.
|
||||
|
||||
Returns:
|
||||
an ordered list of path strings. The path to the Julia
|
||||
executable is added to the list.
|
||||
|
||||
"""
|
||||
return super().get_path() + ['${JULIA_HOME}']
|
||||
|
||||
def get_build_scripts(self):
|
||||
"""
|
||||
Return series of build-steps common to "ALL" Julia repositories
|
||||
|
||||
All scripts found here should be independent of contents of a
|
||||
particular repository.
|
||||
|
||||
This creates a directory with permissions for installing julia packages
|
||||
(from get_assemble_scripts).
|
||||
|
||||
"""
|
||||
return super().get_build_scripts() + [
|
||||
(
|
||||
"root",
|
||||
r"""
|
||||
mkdir -p ${JULIA_PATH} && \
|
||||
curl -sSL "https://julialang-s3.julialang.org/bin/linux/x64/${JULIA_VERSION%[.-]*}/julia-${JULIA_VERSION}-linux-x86_64.tar.gz" | tar -xz -C ${JULIA_PATH} --strip-components 1
|
||||
"""
|
||||
),
|
||||
(
|
||||
"root",
|
||||
r"""
|
||||
mkdir -p ${JULIA_PKGDIR} && \
|
||||
chown ${NB_USER}:${NB_USER} ${JULIA_PKGDIR}
|
||||
"""
|
||||
),
|
||||
(
|
||||
"${NB_USER}",
|
||||
# HACK: Can't seem to tell IJulia to install in sys-prefix
|
||||
# FIXME: Find way to get it to install under /srv and not $HOME?
|
||||
r"""
|
||||
julia -e 'if (VERSION > v"0.7-") using Pkg; else Pkg.init(); end; Pkg.add("IJulia"); using IJulia;' && \
|
||||
mv ${HOME}/.local/share/jupyter/kernels/julia-${JULIA_VERSION%[.-]*} ${NB_PYTHON_PREFIX}/share/jupyter/kernels/julia-${JULIA_VERSION%[.-]*}
|
||||
"""
|
||||
)
|
||||
]
|
||||
|
||||
def get_assemble_scripts(self):
|
||||
"""
|
||||
Return series of build-steps specific to "this" Julia repository
|
||||
|
||||
Precompile all Julia libraries found in the repository's REQUIRE
|
||||
file. The parent, CondaBuildPack, will add the build steps for
|
||||
any needed Python packages found in environment.yml.
|
||||
|
||||
"""
|
||||
require = self.binder_path('REQUIRE')
|
||||
return super().get_assemble_scripts() + [(
|
||||
"${NB_USER}",
|
||||
# Install and pre-compile all libraries if they've opted into it.
|
||||
# In v0.6, Pkg.resolve() installs all the packages, but in v0.7+, we
|
||||
# have to manually Pkg.add() each of them (since the REQUIRES file
|
||||
# format is deprecated).
|
||||
# The precompliation is done via `using {libraryname}`.
|
||||
r"""
|
||||
julia /tmp/install-repo-dependencies.jl "%(require)s"
|
||||
""" % {"require": require}
|
||||
# TODO: For some reason, `rm`ing the file fails with permission denied.
|
||||
# && rm /tmp/install-repo-dependencies.jl
|
||||
)]
|
||||
|
||||
def get_build_script_files(self):
|
||||
files = {
|
||||
'julia/install-repo-dependencies.jl': '/tmp/install-repo-dependencies.jl',
|
||||
}
|
||||
files.update(super().get_build_script_files())
|
||||
return files
|
||||
|
||||
def detect(self):
|
||||
"""
|
||||
Check if current repo should be built with the Julia Build pack
|
||||
|
||||
super().detect() is not called in this function - it would return
|
||||
false unless an `environment.yml` is present and we do not want to
|
||||
require the presence of a `environment.yml` to use Julia.
|
||||
|
||||
Instead we just check if the path to `REQUIRE` exists
|
||||
|
||||
"""
|
||||
# TODO(nhdaly): Add support for Project.toml here as well.
|
||||
return os.path.exists(self.binder_path('REQUIRE'))
|
||||
from .julia_project import JuliaProjectTomlBuildPack
|
||||
from .julia_require import JuliaRequireBuildPack
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
"""Generates a Dockerfile based on an input matrix for Julia"""
|
||||
import os
|
||||
import toml
|
||||
from ..python import PythonBuildPack
|
||||
from .julia_semver import find_semver_match
|
||||
|
||||
class JuliaProjectTomlBuildPack(PythonBuildPack):
|
||||
"""
|
||||
Julia build pack which uses conda.
|
||||
"""
|
||||
|
||||
# ALL EXISTING JULIA VERSIONS
|
||||
# Note that these must remain ordered, in order for the find_semver_match()
|
||||
# function to behave correctly.
|
||||
all_julias = [
|
||||
"0.7.0",
|
||||
"1.0.0", "1.0.1", "1.0.2", "1.0.3",
|
||||
"1.1.0",
|
||||
]
|
||||
|
||||
@property
|
||||
def julia_version(self):
|
||||
default_julia_version = self.all_julias[-1]
|
||||
|
||||
if os.path.exists(self.binder_path('JuliaProject.toml')):
|
||||
project_toml = toml.load(self.binder_path('JuliaProject.toml'))
|
||||
else:
|
||||
project_toml = toml.load(self.binder_path('Project.toml'))
|
||||
|
||||
if 'compat' in project_toml:
|
||||
if 'julia' in project_toml['compat']:
|
||||
julia_version_str = project_toml['compat']['julia']
|
||||
|
||||
# For Project.toml files, install the latest julia version that
|
||||
# satisfies the given semver.
|
||||
_julia_version = find_semver_match(julia_version_str, self.all_julias)
|
||||
if _julia_version is not None:
|
||||
return _julia_version
|
||||
|
||||
return default_julia_version
|
||||
|
||||
def get_build_env(self):
|
||||
"""Get additional environment settings for Julia and Jupyter
|
||||
|
||||
Returns:
|
||||
an ordered list of environment setting tuples
|
||||
|
||||
The tuples contain a string of the environment variable name and
|
||||
a string of the environment setting:
|
||||
- `JULIA_PATH`: base path where all Julia Binaries and libraries
|
||||
will be installed
|
||||
- `JULIA_BINDIR`: path where all Julia Binaries will be installed
|
||||
- `JULIA_DEPOT_PATH`: path where Julia libraries are installed.
|
||||
- `JULIA_VERSION`: default version of julia to be installed
|
||||
- `JUPYTER`: environment variable required by IJulia to point to
|
||||
the `jupyter` executable
|
||||
|
||||
For example, a tuple may be `('JULIA_VERSION', '0.6.0')`.
|
||||
|
||||
"""
|
||||
return super().get_build_env() + [
|
||||
('JULIA_PATH', '${APP_BASE}/julia'),
|
||||
('JULIA_BINDIR', '${JULIA_PATH}/bin'),
|
||||
('JULIA_DEPOT_PATH', '${JULIA_PATH}/pkg'),
|
||||
('JULIA_VERSION', self.julia_version),
|
||||
('JUPYTER', '${NB_PYTHON_PREFIX}/bin/jupyter')
|
||||
]
|
||||
|
||||
def get_path(self):
|
||||
"""Adds path to Julia binaries to user's PATH.
|
||||
|
||||
Returns:
|
||||
an ordered list of path strings. The path to the Julia
|
||||
executable is added to the list.
|
||||
|
||||
"""
|
||||
return super().get_path() + ['${JULIA_BINDIR}']
|
||||
|
||||
def get_build_scripts(self):
|
||||
"""
|
||||
Return series of build-steps common to "ALL" Julia repositories
|
||||
|
||||
All scripts found here should be independent of contents of a
|
||||
particular repository.
|
||||
|
||||
This creates a directory with permissions for installing julia packages
|
||||
(from get_assemble_scripts).
|
||||
|
||||
"""
|
||||
return super().get_build_scripts() + [
|
||||
(
|
||||
"root",
|
||||
r"""
|
||||
mkdir -p ${JULIA_PATH} && \
|
||||
curl -sSL "https://julialang-s3.julialang.org/bin/linux/x64/${JULIA_VERSION%[.-]*}/julia-${JULIA_VERSION}-linux-x86_64.tar.gz" | tar -xz -C ${JULIA_PATH} --strip-components 1
|
||||
"""
|
||||
),
|
||||
(
|
||||
"root",
|
||||
r"""
|
||||
mkdir -p ${JULIA_DEPOT_PATH} && \
|
||||
chown ${NB_USER}:${NB_USER} ${JULIA_DEPOT_PATH}
|
||||
"""
|
||||
),
|
||||
]
|
||||
|
||||
def get_assemble_scripts(self):
|
||||
"""
|
||||
Return series of build-steps specific to "this" Julia repository
|
||||
|
||||
Instantiate and then precompile all packages in the repos julia
|
||||
environment.
|
||||
|
||||
The parent, CondaBuildPack, will add the build steps for
|
||||
any needed Python packages found in environment.yml.
|
||||
"""
|
||||
return super().get_assemble_scripts() + [
|
||||
(
|
||||
"${NB_USER}",
|
||||
r"""
|
||||
julia -e "using Pkg; Pkg.add(\"IJulia\"); using IJulia; installkernel(\"Julia\", \"--project=${REPO_DIR}\", env=Dict(\"JUPYTER_DATA_DIR\"=>\"${NB_PYTHON_PREFIX}/share/jupyter\"));" && \
|
||||
julia --project=${REPO_DIR} -e 'using Pkg; Pkg.instantiate(); pkg"precompile"'
|
||||
"""
|
||||
)
|
||||
]
|
||||
|
||||
def detect(self):
|
||||
"""
|
||||
Check if current repo should be built with the Julia Build pack
|
||||
|
||||
super().detect() is not called in this function - it would return
|
||||
false unless an `environment.yml` is present and we do not want to
|
||||
require the presence of a `environment.yml` to use Julia.
|
||||
|
||||
Instead we just check if the path to `Project.toml` or
|
||||
`JuliaProject.toml` exists.
|
||||
|
||||
"""
|
||||
return os.path.exists(self.binder_path('Project.toml')) or os.path.exists(self.binder_path('JuliaProject.toml'))
|
|
@ -0,0 +1,170 @@
|
|||
"""Generates a Dockerfile based on an input matrix with REQUIRE for legacy Julia"""
|
||||
import os
|
||||
from ..python import PythonBuildPack
|
||||
|
||||
|
||||
class JuliaRequireBuildPack(PythonBuildPack):
|
||||
"""
|
||||
Julia build pack which uses conda and REQUIRE.
|
||||
"""
|
||||
|
||||
minor_julias = {
|
||||
'0.6': '0.6.4',
|
||||
'0.7': '0.7.0',
|
||||
'1.0': '1.0.3',
|
||||
'1.1': '1.1.0',
|
||||
}
|
||||
major_julias = {
|
||||
'1': '1.1.0',
|
||||
}
|
||||
|
||||
@property
|
||||
def julia_version(self):
|
||||
require = self.binder_path('REQUIRE')
|
||||
try:
|
||||
with open(require) as f:
|
||||
julia_version_line = f.readline().strip() # First line is optionally a julia version
|
||||
except FileNotFoundError:
|
||||
julia_version_line = ''
|
||||
|
||||
if not julia_version_line.startswith('julia '):
|
||||
# not a Julia version line.
|
||||
# use the default Julia.
|
||||
self._julia_version = self.minor_julias['0.6']
|
||||
return self._julia_version
|
||||
|
||||
julia_version_info = julia_version_line.split(' ', 1)[1].split('.')
|
||||
julia_version = ''
|
||||
if len(julia_version_info) == 1:
|
||||
julia_version = self.major_julias[julia_version_info[0]]
|
||||
elif len(julia_version_info) == 2:
|
||||
# get major.minor
|
||||
julia_version = self.minor_julias['.'.join(julia_version_info)]
|
||||
else:
|
||||
# use supplied julia version
|
||||
julia_version = '.'.join(julia_version_info)
|
||||
self._julia_version = julia_version
|
||||
return self._julia_version
|
||||
|
||||
def get_build_env(self):
|
||||
"""Get additional environment settings for Julia and Jupyter
|
||||
|
||||
Returns:
|
||||
an ordered list of environment setting tuples
|
||||
|
||||
The tuples contain a string of the environment variable name and
|
||||
a string of the environment setting:
|
||||
- `JULIA_PATH`: base path where all Julia Binaries and libraries
|
||||
will be installed
|
||||
- `JULIA_HOME`: path where all Julia Binaries will be installed
|
||||
- `JULIA_PKGDIR`: path where all Julia libraries will be installed
|
||||
- `JULIA_DEPOT_PATH`: path where Julia libraries are installed.
|
||||
Similar to JULIA_PKGDIR, used in 1.x.
|
||||
- `JULIA_VERSION`: default version of julia to be installed
|
||||
- `JUPYTER`: environment variable required by IJulia to point to
|
||||
the `jupyter` executable
|
||||
|
||||
For example, a tuple may be `('JULIA_VERSION', '0.6.0')`.
|
||||
|
||||
"""
|
||||
return super().get_build_env() + [
|
||||
('JULIA_PATH', '${APP_BASE}/julia'),
|
||||
('JULIA_HOME', '${JULIA_PATH}/bin'), # julia <= 0.6
|
||||
('JULIA_BINDIR', '${JULIA_HOME}'), # julia >= 0.7
|
||||
('JULIA_PKGDIR', '${JULIA_PATH}/pkg'),
|
||||
('JULIA_DEPOT_PATH', '${JULIA_PKGDIR}'), # julia >= 0.7
|
||||
('JULIA_VERSION', self.julia_version),
|
||||
('JUPYTER', '${NB_PYTHON_PREFIX}/bin/jupyter')
|
||||
]
|
||||
|
||||
def get_path(self):
|
||||
"""Adds path to Julia binaries to user's PATH.
|
||||
|
||||
Returns:
|
||||
an ordered list of path strings. The path to the Julia
|
||||
executable is added to the list.
|
||||
|
||||
"""
|
||||
return super().get_path() + ['${JULIA_HOME}']
|
||||
|
||||
def get_build_scripts(self):
|
||||
"""
|
||||
Return series of build-steps common to "ALL" Julia repositories
|
||||
|
||||
All scripts found here should be independent of contents of a
|
||||
particular repository.
|
||||
|
||||
This creates a directory with permissions for installing julia packages
|
||||
(from get_assemble_scripts).
|
||||
|
||||
"""
|
||||
return super().get_build_scripts() + [
|
||||
(
|
||||
"root",
|
||||
r"""
|
||||
mkdir -p ${JULIA_PATH} && \
|
||||
curl -sSL "https://julialang-s3.julialang.org/bin/linux/x64/${JULIA_VERSION%[.-]*}/julia-${JULIA_VERSION}-linux-x86_64.tar.gz" | tar -xz -C ${JULIA_PATH} --strip-components 1
|
||||
"""
|
||||
),
|
||||
(
|
||||
"root",
|
||||
r"""
|
||||
mkdir -p ${JULIA_PKGDIR} && \
|
||||
chown ${NB_USER}:${NB_USER} ${JULIA_PKGDIR}
|
||||
"""
|
||||
),
|
||||
(
|
||||
"${NB_USER}",
|
||||
# HACK: Can't seem to tell IJulia to install in sys-prefix
|
||||
# FIXME: Find way to get it to install under /srv and not $HOME?
|
||||
r"""
|
||||
julia -e 'if (VERSION > v"0.7-") using Pkg; else Pkg.init(); end; Pkg.add("IJulia"); using IJulia;' && \
|
||||
mv ${HOME}/.local/share/jupyter/kernels/julia-${JULIA_VERSION%[.-]*} ${NB_PYTHON_PREFIX}/share/jupyter/kernels/julia-${JULIA_VERSION%[.-]*}
|
||||
"""
|
||||
)
|
||||
]
|
||||
|
||||
def get_assemble_scripts(self):
|
||||
"""
|
||||
Return series of build-steps specific to "this" Julia repository
|
||||
|
||||
Precompile all Julia libraries found in the repository's REQUIRE
|
||||
file. The parent, CondaBuildPack, will add the build steps for
|
||||
any needed Python packages found in environment.yml.
|
||||
|
||||
"""
|
||||
require = self.binder_path('REQUIRE')
|
||||
return super().get_assemble_scripts() + [(
|
||||
"${NB_USER}",
|
||||
# Install and pre-compile all libraries if they've opted into it.
|
||||
# In v0.6, Pkg.resolve() installs all the packages, but in v0.7+, we
|
||||
# have to manually Pkg.add() each of them (since the REQUIRES file
|
||||
# format is deprecated).
|
||||
# The precompliation is done via `using {libraryname}`.
|
||||
r"""
|
||||
julia /tmp/install-repo-dependencies.jl "%(require)s"
|
||||
""" % {"require": require}
|
||||
# TODO: For some reason, `rm`ing the file fails with permission denied.
|
||||
# && rm /tmp/install-repo-dependencies.jl
|
||||
)]
|
||||
|
||||
def get_build_script_files(self):
|
||||
files = {
|
||||
'julia/install-repo-dependencies.jl': '/tmp/install-repo-dependencies.jl',
|
||||
}
|
||||
files.update(super().get_build_script_files())
|
||||
return files
|
||||
|
||||
def detect(self):
|
||||
"""
|
||||
Check if current repo should be built with the Julia Legacy Build pack
|
||||
|
||||
super().detect() is not called in this function - it would return
|
||||
false unless an `environment.yml` is present and we do not want to
|
||||
require the presence of a `environment.yml` to use Julia.
|
||||
|
||||
Instead we just check if the path to `REQUIRE` exists and that there is
|
||||
no julia 1.0 style environment
|
||||
|
||||
"""
|
||||
return os.path.exists(self.binder_path('REQUIRE')) and not(os.path.exists(self.binder_path('Project.toml')) or os.path.exists(self.binder_path('JuliaProject.toml')))
|
|
@ -0,0 +1,119 @@
|
|||
# This file implements the julia-specific logic for handling SemVer (Semantic
|
||||
# Versioning) strings in .toml files.
|
||||
#
|
||||
# It uses the python "semver" package to do most version string comparisons, but
|
||||
# the places where julia's SemVer handling differs from the semver package have
|
||||
# been implemented directly.
|
||||
#
|
||||
# Here, we use tuples to represent a Version, and functors as "matchers". The matcher
|
||||
# functors take a version string and return true if it passes its constraints.
|
||||
|
||||
import re
|
||||
import semver
|
||||
|
||||
# Main algorithm:
|
||||
# Create an AbstractMatcher instance from the constraint string, and check it
|
||||
# against each version in the versions_list, returning the first success.
|
||||
def find_semver_match(constraint, versions_list):
|
||||
matcher = create_semver_matcher(constraint)
|
||||
for vstr in reversed(versions_list):
|
||||
if matcher.match(str_to_version(vstr)):
|
||||
return vstr
|
||||
return None
|
||||
|
||||
def str_to_version(vstr):
|
||||
return tuple([int(n) for n in vstr.split('.')])
|
||||
|
||||
# --- Matcher interface -------------------------------------------
|
||||
|
||||
class AbstractMatcher:
|
||||
def match(self, v):
|
||||
pass
|
||||
|
||||
class SemverMatcher(AbstractMatcher):
|
||||
""" Match a version tuple to a given constraint_str using the `semver` package. """
|
||||
def __init__(self, constraint_str):
|
||||
self.constraint_str = constraint_str
|
||||
def match(self, v):
|
||||
while len(v) < 3:
|
||||
v = v+(0,)
|
||||
v_str = '.'.join(map(str, v))
|
||||
return semver.match(v_str, self.constraint_str)
|
||||
def __repr__(self):
|
||||
return self.constraint_str
|
||||
|
||||
# --- Custom matcher for julia-specific SemVer handling: ---------
|
||||
|
||||
from enum import Enum
|
||||
class Exclusivity(Enum):
|
||||
EXCLUSIVE = 1
|
||||
INCLUSIVE = 2
|
||||
|
||||
class VersionRange(AbstractMatcher):
|
||||
""" Match a version tuple between lower and upper bounds. """
|
||||
def __init__(self, lower, upper, upper_exclusivity):
|
||||
self.lower = lower
|
||||
self.upper = upper
|
||||
self.upper_exclusivity = upper_exclusivity
|
||||
def match(self, v):
|
||||
if self.upper_exclusivity == Exclusivity.EXCLUSIVE:
|
||||
return self.lower <= v < self.upper
|
||||
else:
|
||||
return self.lower <= v <= self.upper
|
||||
def __repr__(self):
|
||||
return ("["+".".join(map(str, self.lower)) +"-"+ ".".join(map(str, self.upper)) +
|
||||
(")" if self.upper_exclusivity == Exclusivity.EXCLUSIVE else "]"))
|
||||
|
||||
# Helpers
|
||||
def major(v): return v[0]
|
||||
def minor(v): return v[1] if len(v) >= 2 else 0
|
||||
def patch(v): return v[2] if len(v) >= 3 else 0
|
||||
|
||||
# --- main constraint parser function ------------------------------------
|
||||
|
||||
def create_semver_matcher(constraint_str):
|
||||
"""
|
||||
Returns a derived-class instance of AbstractMatcher that matches version
|
||||
tuples against the provided constraint_str.
|
||||
"""
|
||||
constraint_str = constraint_str.strip()
|
||||
first_digit = re.search(r"\d", constraint_str)
|
||||
if not first_digit:
|
||||
# Invalid version string (no numbers in it)
|
||||
return ""
|
||||
constraint = str_to_version(constraint_str[first_digit.start():])
|
||||
|
||||
comparison_symbol = constraint_str[0:first_digit.start()].strip()
|
||||
|
||||
# Default to "^" search if no matching mode specified (up to next major version)
|
||||
if (first_digit.start() == 0) or (comparison_symbol == "^"):
|
||||
if major(constraint) == 0:
|
||||
# Also, julia treats pre-1.0 releases specially, as if the first
|
||||
# non-zero number is actually a major number:
|
||||
# https://docs.julialang.org/en/latest/stdlib/Pkg/#Caret-specifiers-1
|
||||
# So we need to handle it separately by bumping the first non-zero number.
|
||||
for i,n in enumerate(constraint):
|
||||
if n != 0 or i == len(constraint)-1: # (using the last existing number handles situations like "^0.0" or "^0")
|
||||
upper = constraint[0:i] + (n+1,)
|
||||
break
|
||||
return VersionRange(constraint, upper, Exclusivity.EXCLUSIVE)
|
||||
else:
|
||||
return VersionRange(constraint, (major(constraint)+1,), Exclusivity.EXCLUSIVE)
|
||||
|
||||
# '~' matching (only allowed to bump the last present number by one)
|
||||
if (comparison_symbol == "~"):
|
||||
return VersionRange(constraint, constraint[:-1] +(constraint[-1]+1,), Exclusivity.INCLUSIVE)
|
||||
|
||||
# Use semver package's comparisons for everything else:
|
||||
|
||||
# semver requires three version numbers
|
||||
if len(constraint) < 3:
|
||||
while len(constraint) < 3:
|
||||
constraint = constraint+(0,)
|
||||
constraint_str = constraint_str[0:first_digit.start()] + ".".join(map(str, constraint))
|
||||
|
||||
# Convert special comparison strings to format accepted by `semver` library.
|
||||
constraint_str = constraint_str.replace("≥", ">=").replace("≤", "<=")
|
||||
constraint_str = re.sub(r"(^|\b)=\b", "==", constraint_str)
|
||||
|
||||
return SemverMatcher(constraint_str)
|
2
setup.py
2
setup.py
|
@ -18,6 +18,8 @@ setup(
|
|||
'escapism',
|
||||
'jinja2',
|
||||
'ruamel.yaml>=0.15',
|
||||
'toml',
|
||||
'semver',
|
||||
],
|
||||
python_requires='>=3.4',
|
||||
author='Project Jupyter Contributors',
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[deps]
|
||||
IteratorInterfaceExtensions = "82899510-4779-5014-852e-03e436cf321d"
|
||||
|
||||
[compat]
|
||||
julia = "=1.0.2"
|
|
@ -0,0 +1,4 @@
|
|||
Julia - Project.toml: test version for julia v1.0.2
|
||||
---------------------------------------------------
|
||||
|
||||
Test a Project.toml file for julia.
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env julia
|
||||
# Verify the version:
|
||||
if VERSION != v"1.0.2"
|
||||
exit(1)
|
||||
end
|
||||
|
||||
using Pkg
|
||||
pkg"activate ."
|
||||
|
||||
try
|
||||
# Test that the package was installed.
|
||||
using IteratorInterfaceExtensions
|
||||
|
||||
# Verify that the environment variables are set correctly for julia 1.0+
|
||||
@assert "julia" ∈ readdir(Sys.BINDIR)
|
||||
catch
|
||||
exit(1)
|
||||
end
|
||||
|
||||
exit(0)
|
|
@ -0,0 +1,2 @@
|
|||
[deps]
|
||||
IteratorInterfaceExtensions = "82899510-4779-5014-852e-03e436cf321d"
|
|
@ -0,0 +1,4 @@
|
|||
Julia - Project.toml: test version for julia v1.1
|
||||
-------------------------------------------------
|
||||
|
||||
Test a Project.toml file for julia.
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env julia
|
||||
# Verify the version:
|
||||
if VERSION < v"1.1.0"
|
||||
exit(1)
|
||||
end
|
||||
|
||||
using Pkg
|
||||
pkg"activate ."
|
||||
|
||||
try
|
||||
# Test that the package was installed.
|
||||
using IteratorInterfaceExtensions
|
||||
|
||||
# Verify that the environment variables are set correctly for julia 1.0+
|
||||
@assert "julia" ∈ readdir(Sys.BINDIR)
|
||||
catch
|
||||
exit(1)
|
||||
end
|
||||
|
||||
exit(0)
|
Ładowanie…
Reference in New Issue