diff --git a/.gitignore b/.gitignore
index f037700b..22437b93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,5 @@
*.pyc
*.un~
/build/
-/deps/deps.jl
-/deps/build.log
-/deps/JUPYTER
-/deps/julia-*
*.jl.*.cov
Manifest*.toml
diff --git a/Project.toml b/Project.toml
index 73d175c6..1ec616f9 100644
--- a/Project.toml
+++ b/Project.toml
@@ -15,6 +15,7 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
+Scratch = "6c6a2e73-6563-6170-7368-637461726353"
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
ZMQ = "c2297ded-f4af-51ae-bb23-16f91089e4e1"
@@ -42,6 +43,7 @@ REPL = "1"
Random = "1"
Revise = "3"
SHA = "0.7, 1"
+Scratch = "1.3.0"
Sockets = "1"
UUIDs = "1"
ZMQ = "1.4"
diff --git a/README.md b/README.md
index 35df1678..4a584055 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-

+
[](https://JuliaLang.github.io/IJulia.jl/stable)
[](https://JuliaLang.github.io/IJulia.jl/dev)
@@ -31,29 +31,26 @@ Install IJulia from the Julia REPL by pressing `]` to enter pkg mode and enterin
add IJulia
```
-If you already have Python/Jupyter installed on your machine, this process will also install a
-[kernel specification](https://jupyter-client.readthedocs.io/en/latest/kernels.html#kernelspecs)
-that tells Jupyter how to launch Julia. You can then launch the notebook server the usual
-way by running `jupyter notebook` in the terminal.
-
-Note that `IJulia` should generally be installed in Julia's global package environment, unless you
-install a custom kernel that specifies a particular environment.
-
-Alternatively, you can have IJulia create and manage its own Python/Jupyter installation.
-To do this, type the following in Julia, at the `julia>` prompt:
+To launch the Jupyter notebook, type the following in Julia at the `julia>` prompt:
```julia
using IJulia
notebook()
```
-to launch the IJulia notebook in your browser.
-The first time you run `notebook()`, it will prompt you
-for whether it should install Jupyter. Hit enter to
-have it use the [Conda.jl](https://github.com/Luthaf/Conda.jl)
-package to install a minimal Python+Jupyter distribution (via
-[Miniconda](https://www.anaconda.com/docs/getting-started/miniconda/install#quickstart-install-instructions)) that is
-private to Julia (not in your `PATH`).
+The first time you run `notebook()`, it will:
+- Prompt you to install Jupyter if you don't already have it (hit enter to
+ install via [Conda.jl](https://github.com/JuliaPy/Conda.jl), which creates a
+ minimal Python+Jupyter distribution private to Julia).
+- Automatically install the Julia kernel for your current Julia version.
+
+If you already have Jupyter installed and prefer to use it, you can launch it
+from the terminal with `jupyter notebook` instead. A Julia kernel can be
+installed with `IJulia.installkernel()`.
+
+**Note:** IJulia should generally be installed in Julia's global package
+environment, unless you install a custom kernel that specifies a particular
+environment.
For more advanced installation options, such as specifying a specific Jupyter
installation to use, see the [documentation](https://JuliaLang.github.io/IJulia.jl/stable).
diff --git a/deps/ijulialogo.png b/assets/ijulialogo.png
similarity index 100%
rename from deps/ijulialogo.png
rename to assets/ijulialogo.png
diff --git a/deps/logo-32x32.png b/assets/logo-32x32.png
similarity index 100%
rename from deps/logo-32x32.png
rename to assets/logo-32x32.png
diff --git a/deps/logo-64x64.png b/assets/logo-64x64.png
similarity index 100%
rename from deps/logo-64x64.png
rename to assets/logo-64x64.png
diff --git a/deps/logo-svg.svg b/assets/logo-svg.svg
similarity index 100%
rename from deps/logo-svg.svg
rename to assets/logo-svg.svg
diff --git a/deps/build.jl b/deps/build.jl
deleted file mode 100644
index 59dd63af..00000000
--- a/deps/build.jl
+++ /dev/null
@@ -1,45 +0,0 @@
-using Conda
-
-include("kspec.jl")
-if !haskey(ENV, "IJULIA_NODEFAULTKERNEL")
- # Install Jupyter kernel-spec file.
- kernelpath = installkernel("Julia", "--project=@.")
-end
-
-# make it easier to get more debugging output by setting JULIA_DEBUG=1
-# when building.
-IJULIA_DEBUG = lowercase(get(ENV, "IJULIA_DEBUG", "0"))
-IJULIA_DEBUG = IJULIA_DEBUG in ("1", "true", "yes")
-
-# remember the user's Jupyter preference, if any; empty == Conda
-prefsfile = joinpath(first(DEPOT_PATH), "prefs", "IJulia")
-mkpath(dirname(prefsfile))
-jupyter = get(ENV, "JUPYTER", isfile(prefsfile) ? readchomp(prefsfile) : Sys.isunix() && !Sys.isapple() ? "jupyter" : "")
-condajupyter = normpath(Conda.SCRIPTDIR, exe("jupyter"))
-if isempty(jupyter) || dirname(jupyter) == abspath(Conda.SCRIPTDIR)
- jupyter = condajupyter # will be installed if needed
-elseif isabspath(jupyter)
- if !Sys.isexecutable(jupyter)
- @warn("ignoring non-executable JUPYTER=$jupyter")
- jupyter = condajupyter
- end
-elseif jupyter != basename(jupyter) # relative path
- @warn("ignoring relative path JUPYTER=$jupyter")
- jupyter = condajupyter
-elseif Sys.which(jupyter) === nothing
- @warn("JUPYTER=$jupyter not found in PATH")
-end
-
-function write_if_changed(filename, contents)
- if !isfile(filename) || read(filename, String) != contents
- write(filename, contents)
- end
-end
-
-# Install the deps.jl file:
-deps = """
- const IJULIA_DEBUG = $(IJULIA_DEBUG)
- const JUPYTER = $(repr(jupyter))
-"""
-write_if_changed("deps.jl", deps)
-write_if_changed(prefsfile, jupyter)
diff --git a/docs/src/_changelog.md b/docs/src/_changelog.md
index b982f47e..a1a32bdc 100644
--- a/docs/src/_changelog.md
+++ b/docs/src/_changelog.md
@@ -7,6 +7,32 @@ CurrentModule = IJulia
This documents notable changes in IJulia.jl. The format is based on [Keep a
Changelog](https://keepachangelog.com).
+## Unreleased
+
+### Added
+- Added [`IJulia.update_jupyter_path()`](@ref) function to explicitly update the saved
+ Jupyter executable path preference.
+- Added a zero-argument [`installkernel()`](@ref) convenience method that installs
+ the default Julia kernel with `--project=@.`.
+
+### Changed
+- Removed `Pkg.build("IJulia")` support. IJulia no longer uses a
+ build step for kernel installation. The build-time configuration system has
+ been replaced with runtime functions. Use [`installkernel()`](@ref) to install
+ or update kernels, and [`update_jupyter_path()`](@ref) to configure the
+ Jupyter executable path.
+- IJulia now uses [Scratch.jl](https://github.com/JuliaPackaging/Scratch.jl) to
+ store configuration preferences instead of instead of writing to
+ `DEPOT_PATH/prefs/`.
+- Kernel auto-installation (when calling `notebook()` or `jupyterlab()`) now
+ checks if the **default** kernel for the current Julia version exists.
+ If not, it automatically installs it. This auto-installation can be disabled
+ by setting the `IJULIA_NODEFAULTKERNEL` environment variable. Note that
+ explicit calls to `installkernel()` always install/update a kernel for the
+ current Julia version, regardless of the environment variable.
+- `ENV["IJULIA_DEBUG"]` now automatically enables verbose output for `notebook()` and
+ `jupyterlab()`, making it easier to debug Jupyter launch issues.
+
## [v1.33.0] - 2025-11-22
### Added
@@ -53,6 +79,8 @@ Changelog](https://keepachangelog.com).
patch release of Julia, but it does mean that IJulia will only create kernels
for each Julia minor release instead of each patch release.
+
+
### Fixed
- Fixed the display of `UnionAll` types such as `Pair.body` ([#1203]).
- Fixed a bug in the [PythonCall extension](manual/usage.md#Python-integration)
diff --git a/docs/src/library/public.md b/docs/src/library/public.md
index 7169d5fe..1b62f447 100644
--- a/docs/src/library/public.md
+++ b/docs/src/library/public.md
@@ -7,6 +7,7 @@
IJulia.IJulia
IJulia.inited
IJulia.installkernel
+IJulia.update_jupyter_path
```
diff --git a/docs/src/manual/installation.md b/docs/src/manual/installation.md
index 9707c447..ae3ad6e6 100644
--- a/docs/src/manual/installation.md
+++ b/docs/src/manual/installation.md
@@ -15,17 +15,16 @@
```
!!! info
- This process installs a
- [kernel specification](https://jupyter-client.readthedocs.io/en/latest/kernels.html#kernelspecs)
- for IJulia. The IJulia kernelspec, shorthand for kernel specification,
- contains the instructions for launching a Julia kernel that a notebook
- frontend (Jupyter, JupyterLab, nteract) can use. The kernelspec does not
- install the notebook frontend.
+ The Julia kernel will be automatically installed the first time you run
+ [`IJulia.notebook()`](@ref) or [`IJulia.jupyterlab()`](@ref), or you can
+ install it manually by running [`IJulia.installkernel()`](@ref). The kernel
+ specification contains the instructions for launching a Julia kernel that a
+ notebook frontend (Jupyter, JupyterLab, nteract) can use.
IJulia respects the standard
[`JUPYTER_DATA_DIR`](https://docs.jupyter.org/en/stable/use/jupyter-directories.html#data-files)
- environment variable, so you can set that before installation if you want
- the kernel to be installed in a specific location.
+ environment variable, so you can set that before installing the kernel if you want
+ it to be installed in a specific location.
!!! warning
The command, `Pkg.add("IJulia")`, does not install Jupyter
@@ -34,19 +33,26 @@
You can install Jupyter Notebook by following the Notebook's
installation instructions if you want. Conveniently, Jupyter
Notebook can also be installed automatically when you run
- `IJulia.notebook()`.
+ `IJulia.notebook()`.
See [Running the Julia notebook](running.md#Running-the-IJulia-Notebook).
-
+
You can direct `IJulia.notebook()` to use a specific Jupyter
- installation by setting `ENV["JUPYTER"]` to the path of the
- `jupyter` program executable. This environment variable should
- be set before `Pkg.add` or before running `Pkg.build("IJulia")`,
- and it will remember your preference for subsequent updates.
+ installation by passing the path directly to `IJulia.update_jupyter_path()`,
+ or by setting `ENV["JUPYTER"]` before calling it. This preference will be
+ remembered for subsequent updates. For example:
+ ```julia
+ # Option 1: Pass path directly
+ IJulia.update_jupyter_path("/usr/local/bin/jupyter")
+
+ # Option 2: Set environment variable
+ ENV["JUPYTER"] = "/usr/local/bin/jupyter"
+ IJulia.update_jupyter_path()
+ ```
## Updating Julia and IJulia
Julia is improving rapidly, so it won't be long before you want to
-update your packages or Julia to a more recent version.
+update your packages or Julia to a more recent version.
### Update packages
@@ -66,7 +72,8 @@ for the most recent Julia). If you're using juliaup to manage Julia, then for
every Julia *minor release* (1.11, 1.12, etc) you will need to explicitly update
the IJulia installation to tell Jupyter where to find the new Julia version:
```julia
-Pkg.build("IJulia")
+using IJulia
+IJulia.installkernel()
```
This is because IJulia creates default kernels for every minor version if it
@@ -77,17 +84,13 @@ installation every time you install a new Julia binary (or do anything that
*changes the location of Julia* on your computer).
-!!! important
- `Pkg.build("IJulia")` **must** be run at the Julia command line.
- It will error and fail if run within IJulia.
-
## Installing and customizing kernels
You may find it helpful to run multiple Julia kernels to support different Julia
executable versions and/or environment settings.
You can install one or more custom Julia kernels by using the
-[`IJulia.installkernel`](@ref) function. For example, if you want to run Julia
+[`IJulia.installkernel()`](@ref) function. For example, if you want to run Julia
with all deprecation warnings disabled, you can create a custom IJulia kernel:
```julia
@@ -99,7 +102,7 @@ will be installed (will show up in your main Jupyter kernel menu) that
lets you open notebooks with this flag. Note that the default kernel
that IJulia installs passes the `--project=@.` option to Julia, if you
want to preserve this behaviour for custom kernels make sure to pass it
-explicitly to `IJulia.installkernel`:
+explicitly to [`IJulia.installkernel`](@ref):
```julia
installkernel("Julia nodeps", "--depwarn=no", "--project=@.")
```
@@ -116,23 +119,27 @@ installkernel("Julia (4 threads)", env=Dict("JULIA_NUM_THREADS"=>"4"))
The `env` keyword should be a `Dict` which maps environment variables to values.
-To *prevent* IJulia from installing a default kernel when the package is built,
-define the `IJULIA_NODEFAULTKERNEL` environment variable before adding or
-building IJulia.
+If you want to disable automatic installation of the default kernel (for example, if
+you only want custom kernels), set the `IJULIA_NODEFAULTKERNEL` environment variable:
-## Low-level IPython Installations
+```julia
+using IJulia
-We recommend using IPython 7.15 or later as well as Python 3.
+# Disable auto-installation of the default kernel
+ENV["IJULIA_NODEFAULTKERNEL"] = "true"
-### Using legacy IPython 2.x version
+# Install custom kernels
+IJulia.installkernel("Julia O3", "-O3")
+IJulia.installkernel("Julia (4 threads)", env=Dict("JULIA_NUM_THREADS"=>"4"))
+```
-We recognize that some users may need to use legacy IPython 2.x. You
-can do this by checkout out the `ipython2` branch of the IJulia package:
+With `IJULIA_NODEFAULTKERNEL` set, [`IJulia.notebook()`](@ref) will not
+auto-install the default kernel. You can still manually install the default
+kernel by calling [`IJulia.installkernel()`](@ref) without arguments.
-```julia
-Pkg.checkout("IJulia", "ipython2")
-Pkg.build("IJulia")
-```
+## Low-level IPython Installations
+
+We recommend using IPython 7.15 or later as well as Python 3.
### Manual installation of IPython
@@ -165,8 +172,10 @@ Once IPython 3.0+ and Julia 0.7+ are installed, you can install IJulia from a Ju
Pkg.add("IJulia")
```
-This will download IJulia and a few other prerequisites, and will set up a
-Julia kernel for IPython.
+This will download IJulia and a few other prerequisites. The Julia kernel will be
+automatically installed the first time you run [`IJulia.notebook()`](@ref) or
+[`IJulia.jupyterlab()`](@ref).
If the command above returns an error, you may need to run `Pkg.update()`, then
-retry it, or possibly run `Pkg.build("IJulia")` to force a rebuild.
+retry it. If you need to reinstall the kernel, run
+[`IJulia.installkernel()`](@ref).
diff --git a/docs/src/manual/troubleshooting.md b/docs/src/manual/troubleshooting.md
index bcddfde1..44241b9e 100644
--- a/docs/src/manual/troubleshooting.md
+++ b/docs/src/manual/troubleshooting.md
@@ -16,8 +16,7 @@ Or, explore the data directory relevant to your system, e.g.,
`~/.local/share/jupyter/kernels`.
Make sure that you can find a Julia kernel. If you can't, run
-[`IJulia.installkernel`](@ref), e.g., as `import IJulia;
-IJulia.installkernel("Julia", "--project=@.")` in the Julia REPL.
+[`IJulia.installkernel()`](@ref) in the Julia REPL.
The `kernel.json` file for the `IJulia` kernel should look something like this:
```json
@@ -72,7 +71,7 @@ You can edit the `kernel.json` file to fix any issues. Or, delete the entire
folder containing the `kernel.json` file to start from scratch. This is entirely
safe to do, or you could also use `jupyter kernelspec uninstall ` from the
command line, see `jupyter kernelspec --help`. After deleting an old kernel,
-simply create a new one, using [`IJulia.installkernel`](@ref) from the Julia
+simply create a new one, using [`IJulia.installkernel()`](@ref) from the Julia
REPL.
For further insight into kernel connection issues, look at the error messages
@@ -86,14 +85,14 @@ cf. [Debugging IJulia problems](@ref), below.
## General troubleshooting tips
-* If you ran into a problem with the above steps, after fixing the
- problem you can type `Pkg.build()` to try to rerun the install scripts.
-* If you tried it a while ago, try running `Pkg.update()` and try again:
- this will fetch the latest versions of the Julia packages in case
- the problem you saw was fixed. Run `Pkg.build("IJulia")` if your Julia
- version may have changed. If this doesn't work, you could try just deleting
- the whole `.julia/conda` directory in your home directory (on Windows, it is
- called `Users\USERNAME\.julia\conda` in your home directory) via
+* If you ran into a problem with the above steps, after fixing the problem you
+ can run [`IJulia.installkernel()`](@ref) to try to reinstall the kernel.
+* If you tried it a while ago, try running `Pkg.update()` and try again: this
+ will fetch the latest versions of the Julia packages in case the problem you
+ saw was fixed. Run [`IJulia.installkernel()`](@ref) if your Julia version may
+ have changed. If this doesn't work, you could try just deleting the whole
+ `.julia/conda` directory in your home directory (on Windows, it is called
+ `Users\USERNAME\.julia\conda` in your home directory) via
`rm(abspath(first(DEPOT_PATH), "conda"),recursive=true)` in Julia and
re-adding the packages.
* On MacOS, you currently need MacOS 10.7 or later; [MacOS 10.6 doesn't
@@ -118,7 +117,7 @@ cf. [Debugging IJulia problems](@ref), below.
kernel that uses your required `Project.toml` (see [Julia projects](@ref)).
* Try running `jupyter --version` and make sure that it prints `3.0.0` or
larger; earlier versions of IPython are no longer supported by IJulia.
-* You can try setting `ENV["JUPYTER"]=""; Pkg.build("IJulia")` to force IJulia
+* You can try running `IJulia.update_jupyter_path("")` to force IJulia
to go back to its own Conda-based Jupyter version (if you previously tried a
different `jupyter`).
@@ -127,17 +126,15 @@ cf. [Debugging IJulia problems](@ref), below.
If IJulia is crashing (e.g. it gives you a "kernel appears to have
died" message), you can modify it to print more descriptive error
-messages to the terminal by doing:
+messages to the terminal by setting the `IJULIA_DEBUG` environment variable:
```julia
ENV["IJULIA_DEBUG"]=true
-Pkg.build("IJulia")
```
-Restart the notebook and look for the error message when IJulia dies.
+Restart the notebook, and look for the error message when IJulia dies.
(This changes IJulia to default to `verbose = true` mode, and sets
`capture_stderr = false`, hopefully sending a bunch of debugging to
the terminal where you launched `jupyter`).
-When you are done, set `ENV["IJULIA_DEBUG"]=false` and re-run
-`Pkg.build("IJulia")` to turn off the debugging output.
+When you are done, set `ENV["IJULIA_DEBUG"]=false` to turn off the debugging output.
diff --git a/src/IJulia.jl b/src/IJulia.jl
index 739dd055..c155fc28 100644
--- a/src/IJulia.jl
+++ b/src/IJulia.jl
@@ -48,9 +48,18 @@ import Logging
# and this import makes it possible to load InteractiveUtils from the IJulia namespace
import InteractiveUtils
-const depfile = joinpath(dirname(@__FILE__), "..", "deps", "deps.jl")
-isfile(depfile) || error("IJulia not properly installed. Please run Pkg.build(\"IJulia\")")
-include(depfile) # generated by Pkg.build("IJulia")
+# Conda is a rather heavy dependency so we go to some effort to load it lazily
+const Conda_pkgid = Base.PkgId(Base.UUID("8f4d0f93-b110-5947-807f-2305c1781a2d"), "Conda")
+
+function get_Conda(f::Function)
+ if !haskey(Base.loaded_modules, Conda_pkgid)
+ @eval import Conda
+ end
+ @invokelatest f(Base.loaded_modules[Conda_pkgid])
+end
+
+# Load configuration system
+include("config.jl")
# use our own random seed for msg_id so that we
# don't alter the user-visible random state (issue #336)
@@ -98,7 +107,7 @@ end
REPL.REPLDisplay(repl::MiniREPL) = repl.display
@kwdef mutable struct Kernel
- verbose::Bool = IJULIA_DEBUG
+ verbose::Bool = ijulia_debug()
inited::Bool = false
current_module::Module = Main
shutting_down::Threads.Atomic{Bool} = Threads.Atomic{Bool}(false)
@@ -111,7 +120,7 @@ REPL.REPLDisplay(repl::MiniREPL) = repl.display
n::Int = 0
capture_stdout::Bool = true
- capture_stderr::Bool = !IJULIA_DEBUG
+ capture_stderr::Bool = !ijulia_debug()
capture_stdin::Bool = true
minirepl::Union{MiniREPL, Nothing} = nothing
@@ -312,7 +321,7 @@ function set_current_module(m::Module; kernel=_default_kernel)
end
#######################################################################
-include(joinpath("..", "deps", "kspec.jl"))
+include("kspec.jl")
include("jupyter.jl")
#######################################################################
diff --git a/src/config.jl b/src/config.jl
new file mode 100644
index 00000000..aee2e17c
--- /dev/null
+++ b/src/config.jl
@@ -0,0 +1,126 @@
+using Scratch: @get_scratch!
+
+# Initialized to empty string; will be lazily set on first use (e.g., notebook())
+# or explicitly via update_jupyter_path()
+JUPYTER::String = ""
+
+# Get the IJULIA_DEBUG setting from environment variable.
+function ijulia_debug()
+ debug_val = lowercase(get(ENV, "IJULIA_DEBUG", "0"))
+ return debug_val in ("1", "true", "yes")
+end
+
+# Load the user's Jupyter preference from the Scratch space.
+# Returns the stored Jupyter path, or empty string if not set.
+function load_jupyter_preference()
+ # Check new Scratch.jl location first
+ prefsfile = preference_path("jupyter")
+ if isfile(prefsfile)
+ return readchomp(prefsfile)
+ end
+
+ # Backwards compat with .julia/prefs
+ old_prefsfile = joinpath(first(DEPOT_PATH), "prefs", "IJulia")
+ if isfile(old_prefsfile)
+ return readchomp(old_prefsfile)
+ end
+
+ return ""
+end
+
+# Save the users preference to the Scratch space.
+preference_path(name::String) = joinpath(@get_scratch!("prefs"), name)
+
+function save_preference(name::String, value::String)
+ prefsfile = preference_path(name)
+ mkpath(dirname(prefsfile))
+ write(prefsfile, value)
+ return value
+end
+
+delete_preference(name::String) = rm(preference_path(name); force=true)
+
+# Determine the Jupyter executable path based on environment variables and preferences.
+function determine_jupyter_path()
+ condajupyter = get_Conda() do Conda
+ normpath(Conda.SCRIPTDIR, exe("jupyter"))
+ end
+
+ # Get user preference from environment or stored preference
+ jupyter = get(load_jupyter_preference, ENV, "JUPYTER")
+
+ # Default to "jupyter" on Unix (non-Apple) if nothing is set
+ if isempty(jupyter)
+ jupyter = Sys.isunix() && !Sys.isapple() ? "jupyter" : ""
+ end
+
+ # Validate the jupyter path
+ if !isempty(condajupyter) && (isempty(jupyter) || dirname(jupyter) == abspath(dirname(condajupyter)))
+ jupyter = condajupyter # will be installed if needed
+ elseif isabspath(jupyter)
+ if !Sys.isexecutable(jupyter)
+ @warn("ignoring non-executable JUPYTER=$jupyter")
+ jupyter = condajupyter
+ end
+ elseif jupyter != basename(jupyter) # relative path
+ @warn("ignoring relative path JUPYTER=$jupyter")
+ jupyter = condajupyter
+ end
+
+ return jupyter
+end
+
+"""
+ update_jupyter_path()
+
+Set or determine the Jupyter executable path preference and save it.
+
+The function checks `ENV["JUPYTER"]` first:
+- If not set: uses existing preference, or searches if no preference exists
+- If set to a path: uses that path
+- If set to empty string `""`: deletes existing preference and forces a fresh search
+
+The search checks (in order):
+1. `ENV["JUPYTER"]` environment variable
+2. Previously saved preference (from Scratch.jl)
+3. System default (Conda-based Jupyter or system `jupyter`)
+
+The saved preference will be used by `IJulia.notebook()` and `IJulia.jupyterlab()`.
+
+Returns the Jupyter path that was saved.
+
+## Examples
+```julia
+using IJulia
+
+# Use existing preference (or search if none exists)
+IJulia.update_jupyter_path()
+
+# Explicitly set Jupyter path via ENV
+ENV["JUPYTER"] = "/usr/local/bin/jupyter"
+IJulia.update_jupyter_path()
+
+# Force re-detection, ignoring saved preference
+ENV["JUPYTER"] = ""
+IJulia.update_jupyter_path()
+```
+"""
+function update_jupyter_path()
+ # Check ENV first
+ jupyter = get(ENV, "JUPYTER", nothing)
+
+ if jupyter === nothing
+ # No ENV set - use existing preference or search if none exists
+ jupyter = determine_jupyter_path()
+ elseif isempty(jupyter)
+ # ENV set to "" - delete preference and force fresh search
+ delete_preference("jupyter")
+ jupyter = determine_jupyter_path()
+ end
+ # else: ENV set to specific path - use it
+
+ save_preference("jupyter", jupyter)
+ global JUPYTER = jupyter
+ @info "Jupyter path updated: $jupyter"
+ return jupyter
+end
diff --git a/src/jupyter.jl b/src/jupyter.jl
index 01674443..a872fff9 100644
--- a/src/jupyter.jl
+++ b/src/jupyter.jl
@@ -1,17 +1,6 @@
# Code to launch and interact with Jupyter, not via messaging protocol
##################################################################
-# Conda is a rather heavy dependency so we go to some effort to load it lazily
-const Conda_pkgid = Base.PkgId(Base.UUID("8f4d0f93-b110-5947-807f-2305c1781a2d"), "Conda")
-
-function get_Conda(f::Function)
- if !haskey(Base.loaded_modules, Conda_pkgid)
- @eval import Conda
- end
-
- @invokelatest f(Base.loaded_modules[Conda_pkgid])
-end
-
isyes(s) = isempty(s) || lowercase(strip(s)) in ("y", "yes")
"""
@@ -20,6 +9,10 @@ isyes(s) = isempty(s) || lowercase(strip(s)) in ("y", "yes")
Return a `Cmd` for the program `subcommand`.
"""
function find_jupyter_subcommand(subcommand::AbstractString, port::Union{Nothing,Int}=nothing)
+ global JUPYTER
+ if isempty(JUPYTER)
+ JUPYTER = determine_jupyter_path()
+ end
jupyter = JUPYTER
scriptdir = get_Conda() do Conda
Conda.SCRIPTDIR
@@ -52,6 +45,34 @@ end
##################################################################
+# Check if the default Julia kernel is installed for the current Julia version.
+# If not (and IJULIA_NODEFAULTKERNEL is not set), automatically install it.
+function maybe_install_default_kernel()
+ if haskey(ENV, "IJULIA_NODEFAULTKERNEL")
+ return
+ end
+
+ # Check if the default kernel for the current version exists
+ specname = kernelspec_name("Julia")
+ kernel_path = joinpath(kerneldir(), specname)
+
+ if !isdir(kernel_path)
+ # No default kernel found, install it
+ debugdesc = ccall(:jl_is_debugbuild,Cint,())==1 ? "-debug" : ""
+ @info """
+ No default Julia kernel found for Julia $(VERSION.major).$(VERSION.minor)$(debugdesc).
+ Installing kernel automatically. You can reinstall or update the kernel
+ anytime by running: IJulia.installkernel()
+
+ To disable this auto-installation, set the environment variable:
+ ENV["IJULIA_NODEFAULTKERNEL"] = "true"
+ """
+ installkernel()
+ end
+end
+
+##################################################################
+
"""
launch(cmd, dir, detached, verbose)
@@ -91,6 +112,7 @@ end
function run_subcommand(name, package_name, port, args...)
inited && error("IJulia is already running")
+ maybe_install_default_kernel()
subcmd = find_jupyter_subcommand(name, port)
jupyter = first(subcmd)
scriptdir = get_Conda() do Conda
@@ -111,7 +133,7 @@ function run_subcommand(name, package_name, port, args...)
end
"""
- notebook(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=false)
+ notebook(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=ijulia_debug())
The `notebook()` function launches the Jupyter notebook, and is
equivalent to running `jupyter notebook` at the operating-system
@@ -138,33 +160,34 @@ When the optional keyword `port` is not `nothing`, open the notebook on the
given port number.
If `verbose=true` then the stdout/stderr from Jupyter will be echoed to the
-terminal. Try enabling this if you're having problems connecting to a kernel to
+terminal. By default, this is enabled when `ENV["IJULIA_DEBUG"]` is set.
+Try enabling this if you're having problems connecting to a kernel to
see if there's any useful error messages from Jupyter.
For launching a JupyterLab instance, see [`IJulia.jupyterlab()`](@ref).
"""
-function notebook(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=false)
+function notebook(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=ijulia_debug())
run_subcommand("notebook", "jupyter", port, args, dir, detached, verbose)
end
"""
- jupyterlab(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=false)
+ jupyterlab(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=ijulia_debug())
Similar to [`IJulia.notebook()`](@ref) but launches JupyterLab instead
of the Jupyter notebook.
"""
-function jupyterlab(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=false)
+function jupyterlab(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=ijulia_debug())
run_subcommand("lab", "jupyterlab", port, args, dir, detached, verbose)
end
"""
- nbclassic(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=false)
+ nbclassic(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=ijulia_debug())
Similar to [`IJulia.notebook()`](@ref) but launches the v6
[nbclassic](https://nbclassic.readthedocs.io) notebook instead of the v7
notebook.
"""
-function nbclassic(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=false)
+function nbclassic(args=``; dir=homedir(), detached=false, port::Union{Nothing,Int}=nothing, verbose=ijulia_debug())
run_subcommand("nbclassic", "nbclassic", port, args, dir, detached, verbose)
end
diff --git a/deps/kspec.jl b/src/kspec.jl
similarity index 93%
rename from deps/kspec.jl
rename to src/kspec.jl
index aacc4b4e..4c91ca63 100644
--- a/deps/kspec.jl
+++ b/src/kspec.jl
@@ -1,4 +1,4 @@
-include("jsonx.jl")
+include("../vendor/jsonx.jl")
function print_json_dict(io::IO, dict::AbstractDict)
print(io, '{')
@@ -115,6 +115,14 @@ function julia_cmd(bindir=Sys.BINDIR)
`$(joinpath(bindir, exe("julia")))`
end
+"""
+ installkernel(; kwargs...)
+
+Convenience method that installs the default Julia kernel with `--project=@.`.
+Equivalent to `installkernel("Julia", "--project=@."; kwargs...)`.
+"""
+installkernel(; kwargs...) = installkernel("Julia", "--project=@."; kwargs...)
+
"""
installkernel(name::AbstractString, options::AbstractString...;
julia::Cmd,
@@ -193,9 +201,9 @@ function installkernel(name::AbstractString, julia_options::AbstractString...;
print_json_dict(f, ks)
end
- copy_config(joinpath(ijulia_dir,"deps","logo-32x32.png"), juliakspec)
- copy_config(joinpath(ijulia_dir,"deps","logo-64x64.png"), juliakspec)
- copy_config(joinpath(ijulia_dir,"deps","logo-svg.svg"), juliakspec)
+ copy_config(joinpath(ijulia_dir,"assets","logo-32x32.png"), juliakspec)
+ copy_config(joinpath(ijulia_dir,"assets","logo-64x64.png"), juliakspec)
+ copy_config(joinpath(ijulia_dir,"assets","logo-svg.svg"), juliakspec)
return juliakspec
catch
diff --git a/deps/jsonx.jl b/vendor/jsonx.jl
similarity index 100%
rename from deps/jsonx.jl
rename to vendor/jsonx.jl