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 409337c9..57e818c0 100644
--- a/Project.toml
+++ b/Project.toml
@@ -11,6 +11,7 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
+Preferences = "21216c6a-2e73-6563-6e65-726566657250"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
@@ -34,6 +35,7 @@ Logging = "1"
Markdown = "1"
Pkg = "1"
PrecompileTools = "1.2.1"
+Preferences = "1"
Printf = "1"
PythonCall = "0.9"
REPL = "1"
diff --git a/README.md b/README.md
index 35df1678..fa9e5289 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,20 @@ 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/Luthaf/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. The Julia kernel will be automatically installed the first time you run `IJulia.notebook()` or `IJulia.jupyterlab()`.
+
+**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 61dc419e..edbe7429 100644
--- a/docs/src/_changelog.md
+++ b/docs/src/_changelog.md
@@ -7,6 +7,31 @@ CurrentModule = IJulia
This documents notable changes in IJulia.jl. The format is based on [Keep a
Changelog](https://keepachangelog.com).
+## [Unreleased]
+
+### 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 [Preferences.jl](https://github.com/JuliaPackaging/Preferences.jl) to
+ store configuration preferences (in `LocalPreferences.toml`) instead of Scratch.jl.
+- 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.
+
+### Added
+- Added [`IJulia.update_jupyter_path()`](@ref) function to explicitly update the saved
+ Jupyter executable path preference.
+- Added zero-argument [`installkernel()`](@ref) convenience method that installs
+ the default Julia kernel with `--project=@.`.
+
## [v1.32.0] - 2025-11-04
### Added
@@ -18,6 +43,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 that would break opening comms from
diff --git a/docs/src/library/public.md b/docs/src/library/public.md
index be1fb021..170db84b 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..21215318 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()` or `IJulia.jupyterlab()`, or you can install it manually
+ by running `IJulia.installkernel()`. 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,10 +84,6 @@ 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
@@ -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()` will not auto-install the
+default kernel. You can still manually install the default kernel by calling
+`IJulia.installkernel()` 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,8 @@ 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()` or `IJulia.jupyterlab()`.
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()`.
diff --git a/docs/src/manual/troubleshooting.md b/docs/src/manual/troubleshooting.md
index f709a39b..0c77dd99 100644
--- a/docs/src/manual/troubleshooting.md
+++ b/docs/src/manual/troubleshooting.md
@@ -8,7 +8,7 @@ jupyter kernelspec list
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.
+Make sure that you can find a Julia kernel. If you can't, run `IJulia.installkernel()` to install the default kernel.
The `kernel.json` file for the `IJulia` kernel should look something like this:
@@ -41,7 +41,7 @@ Fundamentally, if the `IJulia` kernel fails to connect, it is most likely due to
* The `julia` executable no longer exists (maybe you updated your installed Julia versions).
* The environment that the `julia` executable runs in does not have the `IJulia` package installed. This is a common error. In general, the `IJulia` package should be installed in the base environment of your Julia installation (what you get when you type `] activate` into the REPL without any further options, or when you simply start the Julia REPL without any options). Note that the `--project=@.` option in the above `kernel.json` falls back to the base environment, so it should generally be safe. If you like to use [shared environments](https://pkgdocs.julialang.org/v1/environments/#Shared-environments), you may want to have a `--project` flag that references that shared environment, and make sure that `IJulia` is installed in that environment. Also make sure that the environment is instantiated.
-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 REPL.
+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 by running `IJulia.installkernel()` for the default kernel, or [`IJulia.installkernel`](@ref) with arguments for a custom kernel.
For further insight into kernel connection issues, look at the error messages emitted by Jupyter. If you started `jupyter lab` / `jupyter notebook` in the terminal, messages will be printed there, not in the web interface that you access via the browser. For more details, you can pass the `--debug` command line flag to `jupyter`. If you started Jupyter via `IJulia.jupyterlab()` / `IJulia.notebook()`, you must also pass `verbose=true` to see any of the output emitted by `jupyter`, including error messages about connection failures; cf. [Debugging IJulia problems](@ref), below.
@@ -50,32 +50,30 @@ For further insight into kernel connection issues, look at the error messages em
## 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.
+ problem you can run `IJulia.installkernel()` 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 `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 `rm(abspath(first(DEPOT_PATH), "conda"),recursive=true)` in Julia and re-adding the packages.
+ the problem you saw was fixed. Run `IJulia.installkernel()` 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 work](https://github.com/JuliaLang/julia/issues/4215) (unless you compile Julia yourself, from source code).
* Internet Explorer 8 (the default in Windows 7) or 9 don't work with the notebook; use Firefox (6 or later) or Chrome (13 or later). Internet Explorer 10 in Windows 8 works (albeit with a few rendering glitches), but Chrome or Firefox is better.
* If the notebook opens up, but doesn't respond (the input label is `In[*]` indefinitely), try creating a new Python notebook (not Julia) from the `New` button in the Jupyter dashboard, to see if `1+1` works in Python. If it is the same problem, then probably you have a [firewall running](https://github.com/ipython/ipython/issues/2499) on your machine (this is common on Windows) and you need to disable the firewall or at least to allow the IP address 127.0.0.1. (For the [Sophos](https://en.wikipedia.org/wiki/Sophos) endpoint security software, go to "Configure Anti-Virus and HIPS", select "Authorization" and then "Websites", and add 127.0.0.1 to "Authorized websites"; finally, restart your computer.) If the Python test works, then IJulia may not be installed in the global or default environment and you may need to install a custom Julia 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 to go back to its own Conda-based Jupyter version (if you previously tried a different `jupyter`).
+* 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`).
## Debugging IJulia problems
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 233b7935..1fd1a647 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
@@ -110,7 +119,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
@@ -309,7 +318,7 @@ end
_shutting_down::Threads.Atomic{Bool} = Threads.Atomic{Bool}(false)
#######################################################################
-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..1855fb7c
--- /dev/null
+++ b/src/config.jl
@@ -0,0 +1,119 @@
+using Preferences: @load_preference, @set_preferences!, @delete_preferences!
+
+# 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.
+# Returns the stored Jupyter path, or empty string if not set.
+function load_jupyter_preference()
+ # Check Preferences.jl location first
+ pref = @load_preference("jupyter", nothing)
+ if pref !== nothing
+ return pref
+ end
+
+ # Backwards compat: check old .julia/prefs location
+ old_prefsfile = joinpath(first(DEPOT_PATH), "prefs", "IJulia")
+ if isfile(old_prefsfile)
+ jupyter = readchomp(old_prefsfile)
+ # Migrate to Preferences.jl
+ @set_preferences!("jupyter" => jupyter)
+ return jupyter
+ end
+
+ return ""
+end
+
+# 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" : condajupyter
+ 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
+ elseif Sys.which(jupyter) === nothing
+ @warn("JUPYTER=$jupyter not found in PATH")
+ 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 LocalPreferences.toml)
+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_preferences!("jupyter")
+ jupyter = determine_jupyter_path()
+ end
+ # else: ENV set to specific path - use it
+
+ @set_preferences!("jupyter" => jupyter)
+ global JUPYTER = jupyter
+ @info "Jupyter path updated: $jupyter"
+ return jupyter
+end
diff --git a/src/jupyter.jl b/src/jupyter.jl
index 5dec4ac8..868a7277 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")
"""
@@ -21,6 +10,9 @@ Return a `Cmd` for the program `subcommand`. If the program is `jupyter` or
`jupyterlab` it may prompt the user to install it.
"""
function find_jupyter_subcommand(subcommand::AbstractString, port::Union{Nothing,Int}=nothing)
+ if isempty(JUPYTER)
+ global JUPYTER = determine_jupyter_path()
+ end
jupyter = JUPYTER
scriptdir = get_Conda() do Conda
Conda.SCRIPTDIR
@@ -63,6 +55,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)
@@ -128,25 +148,28 @@ 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())
inited && error("IJulia is already running")
+ maybe_install_default_kernel()
notebook = find_jupyter_subcommand("notebook", port)
return launch(notebook, 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())
inited && error("IJulia is already running")
+ maybe_install_default_kernel()
lab = find_jupyter_subcommand("lab", port)
jupyter = first(lab)
scriptdir = get_Conda() do Conda
diff --git a/deps/kspec.jl b/src/kspec.jl
similarity index 93%
rename from deps/kspec.jl
rename to src/kspec.jl
index fbdcee94..70387551 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, '{')
@@ -99,6 +99,14 @@ function julia_cmd(bindir=Sys.BINDIR)
end
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,
@@ -177,9 +185,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