Skip to content

Commit

Permalink
feat(cli/tools/jupyter) Add --directory flag to control where kernels…
Browse files Browse the repository at this point in the history
…pec installs

Closes: denoland#20744

Adds ability to specify the kernelspec location for deno kernel with
--directory flag

Continued default behavior installs in user's kernelspec folder via
jupyter shelling out:
`deno jupyter --install`

Advanced installs can specify their own path:
`deno jupyter --install --directory ~/.kernelspec_custom_location/deno`

In the advanced case deno builds and installs the files directly rather
than relying on calling out to jupyter to determine path.

This is useful in the circumstance where jupyter is not on PATH at time
of installing deno jupyter, but it is available and used via a wrapping
library.
  • Loading branch information
zph committed Apr 28, 2024
1 parent 021a0dc commit 4cadcce
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 9 deletions.
32 changes: 32 additions & 0 deletions cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ pub struct JupyterFlags {
pub install: bool,
pub kernel: bool,
pub conn_file: Option<String>,
pub directory: Option<PathBuf>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -2056,6 +2057,14 @@ fn jupyter_subcommand() -> Command {
.value_parser(value_parser!(String))
.value_hint(ValueHint::FilePath)
.conflicts_with("install"))
.arg(
Arg::new("directory")
.long("directory")
.help("Sets the directory to install kernelspec.")
.value_parser(value_parser!(PathBuf))
.value_hint(ValueHint::DirPath)
.requires("install")
)
.about("Deno kernel for Jupyter notebooks")
}

Expand Down Expand Up @@ -3823,11 +3832,17 @@ fn jupyter_parse(flags: &mut Flags, matches: &mut ArgMatches) {
let conn_file = matches.remove_one::<String>("conn");
let kernel = matches.get_flag("kernel");
let install = matches.get_flag("install");
let directory = if matches.contains_id("directory") {
matches.remove_one::<PathBuf>("directory")
} else {
None
};

flags.subcommand = DenoSubcommand::Jupyter(JupyterFlags {
install,
kernel,
conn_file,
directory,
});
}

Expand Down Expand Up @@ -9256,6 +9271,7 @@ mod tests {
install: false,
kernel: false,
conn_file: None,
directory: None,
}),
..Flags::default()
}
Expand All @@ -9269,6 +9285,21 @@ mod tests {
install: true,
kernel: false,
conn_file: None,
directory: None,
}),
..Flags::default()
}
);

let r = flags_from_vec(svec!["deno", "jupyter", "--install", "--directory", "/tmp/deno"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Jupyter(JupyterFlags {
install: true,
kernel: false,
conn_file: None,
directory: Some(PathBuf::from_str("/tmp/deno"))
}),
..Flags::default()
}
Expand All @@ -9288,6 +9319,7 @@ mod tests {
install: false,
kernel: true,
conn_file: Some(String::from("path/to/conn/file")),
directory: None,
}),
..Flags::default()
}
Expand Down
52 changes: 44 additions & 8 deletions cli/tools/jupyter/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::env::current_exe;
use std::io::ErrorKind;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use tempfile::TempDir;

const DENO_ICON_32: &[u8] = include_bytes!("./resources/deno-logo-32x32.png");
Expand Down Expand Up @@ -48,13 +49,39 @@ fn install_icon(
Ok(())
}

pub fn install() -> Result<(), AnyError> {
let temp_dir = TempDir::new().unwrap();
let kernel_json_path = temp_dir.path().join("kernel.json");
pub fn install(directory: Option<PathBuf>) -> Result<(), AnyError> {
let outcome = match directory {
Some(path) => install_via_directory_flag(&path),
None => install_via_jupyter(),
};

if outcome.is_err() {
bail!("🆘 Failed to install Deno kernel");
}

println!("✅ Deno kernelspec installed successfully.");
Ok(())
}

fn install_via_directory_flag(output_dir: &PathBuf) -> Result<(), AnyError> {
let mut dir = output_dir.clone();
if !dir.ends_with("deno") {
dir.push("deno");
}

std::fs::create_dir_all(dir.as_path())?;
create_kernelspec_in_directory(&dir)?;

log::info!("📂 Installing to custom location: {}.", &dir.display());
Ok(())
}

fn create_kernelspec_in_directory(directory: &PathBuf) -> Result<(), AnyError> {
let kernel_json_path = directory.join("kernel.json");
// TODO(bartlomieju): add remaining fields as per
// https://jupyter-client.readthedocs.io/en/stable/kernels.html#kernel-specs
// FIXME(bartlomieju): replace `current_exe` before landing?

let json_data = json!({
"argv": [current_exe().unwrap().to_string_lossy(), "jupyter", "--kernel", "--conn", "{connection_file}"],
"display_name": "Deno",
Expand All @@ -63,9 +90,20 @@ pub fn install() -> Result<(), AnyError> {

let f = std::fs::File::create(kernel_json_path)?;
serde_json::to_writer_pretty(f, &json_data)?;
install_icon(temp_dir.path(), "logo-32x32.png", DENO_ICON_32)?;
install_icon(temp_dir.path(), "logo-64x64.png", DENO_ICON_64)?;
install_icon(temp_dir.path(), "logo-svg.svg", DENO_ICON_SVG)?;
install_icon(directory, "logo-32x32.png", DENO_ICON_32)?;
install_icon(directory, "logo-64x64.png", DENO_ICON_64)?;
install_icon(directory, "logo-svg.svg", DENO_ICON_SVG)?;
Ok(())
}


fn install_via_jupyter() -> Result<(), AnyError> {
let temp_dir = TempDir::new().unwrap();

let create_kernelspec_result = create_kernelspec_in_directory(&temp_dir.path().to_path_buf());
if create_kernelspec_result.is_err() {
return create_kernelspec_result;
}

let child_result = std::process::Command::new("jupyter")
.args([
Expand Down Expand Up @@ -106,8 +144,6 @@ pub fn install() -> Result<(), AnyError> {
bail!("Failed to install kernelspec: {}", err);
}
}

let _ = std::fs::remove_dir(temp_dir);
println!("✅ Deno kernelspec installed successfully.");
Ok(())
}
2 changes: 1 addition & 1 deletion cli/tools/jupyter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub async fn kernel(
}

if jupyter_flags.install {
install::install()?;
install::install(jupyter_flags.directory)?;
return Ok(());
}

Expand Down

0 comments on commit 4cadcce

Please sign in to comment.