@cocalc/project
Version:
CoCalc: project daemon
135 lines • 5.89 kB
JavaScript
;
// This file allows you to run a jupyter kernel via `launch_jupyter_kernel`.
// You have to provide the kernel name and (optionally) launch options for execa [1].
//
// Example:
// import {launch_jupyter_kernel} from "./launch_jupyter_kernel";
// const kernel = await launch_jupyter_kernel("python3", {detached: true, cwd: "/home/user"})
//
// * shell channel: `${kernel.config.ip}:${kernel.config.shell_port}`
// * `kernel.spawn` holds the process and you have to close it when finished.
// * Unless `cleanupConnectionFile` is false, the connection file will be deleted when finished.
//
// Ref:
// [1] execa: https://github.com/sindresorhus/execa#readme
//
// History:
// This is a port of https://github.com/nteract/spawnteract/ to TypeScript (with minor changes).
// Original license: BSD-3-Clause and this file is also licensed under BSD-3-Clause!
// Author: Harald Schilly <hsy@sagemath.com>
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.launch_jupyter_kernel = void 0;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const util_1 = require("util");
const uuid = __importStar(require("uuid"));
const kernelspecs_1 = require("kernelspecs");
const jupyter_paths = __importStar(require("jupyter-paths"));
const portfinder_1 = require("portfinder");
const get_ports = (0, util_1.promisify)(portfinder_1.getPorts);
const jsonfile_1 = require("jsonfile");
const execa_1 = __importDefault(require("execa"));
const mkdirp_1 = __importDefault(require("mkdirp"));
function connection_info(ports) {
return {
version: 5,
key: uuid.v4(),
signature_scheme: "hmac-sha256",
transport: "tcp",
ip: "127.0.0.1",
hb_port: ports[0],
control_port: ports[1],
shell_port: ports[2],
stdin_port: ports[3],
iopub_port: ports[4],
};
}
const DEFAULT_PORT_OPTS = { port: 9000, host: "127.0.0.1" };
// gather the connection information for a kernel, write it to a json file, and return it
async function write_connection_file(port_options) {
const options = { ...DEFAULT_PORT_OPTS, ...port_options };
const ports = await get_ports(5, options);
// Make sure the kernel runtime dir exists before trying to write the kernel file.
const runtimeDir = jupyter_paths.runtimeDir();
await (0, mkdirp_1.default)(runtimeDir);
// Write the kernel connection file -- filename uses the UUID4 key
const config = connection_info(ports);
const connection_file = path.join(runtimeDir, `kernel-${config.key}.json`);
await (0, jsonfile_1.writeFile)(connection_file, config);
return { config, connection_file };
}
// if spawn options' cleanupConnectionFile is true, the connection file is removed
function cleanup(connectionFile) {
try {
fs.unlinkSync(connectionFile);
}
catch (e) {
return;
}
}
const DEFAULT_SPAWN_OPTIONS = {
cleanupConnectionFile: true,
env: {},
};
// actually launch the kernel.
// the returning object contains all the configuration information and in particular,
// `spawn` is the running process started by "execa"
function launch_kernel_spec(kernel_spec, config, connection_file, spawn_options) {
const argv = kernel_spec.argv.map((x) => x.replace("{connection_file}", connection_file));
const full_spawn_options = { ...DEFAULT_SPAWN_OPTIONS, ...spawn_options };
full_spawn_options.env = {
...process.env,
...kernel_spec.env,
...spawn_options.env,
};
const running_kernel = (0, execa_1.default)(argv[0], argv.slice(1), full_spawn_options);
if (full_spawn_options.cleanupConnectionFile !== false) {
running_kernel.on("exit", (_code, _signal) => cleanup(connection_file));
running_kernel.on("error", (_code, _signal) => cleanup(connection_file));
}
return {
spawn: running_kernel,
connection_file,
config,
kernel_spec,
};
}
// for a given kernel name and launch options: prepare the kernel file and launch the process
// optionally, provide cached kernel specs to bypass `findAll()
async function launch_jupyter_kernel(name, spawn_options, cached_specs) {
const specs = cached_specs ?? (await (0, kernelspecs_1.findAll)());
const kernel_spec = specs[name];
if (kernel_spec == null) {
throw new Error(`No spec available for kernel "${name}". Available specs: ${JSON.stringify(Object.keys(specs))}`);
}
const launch_info = await write_connection_file();
return launch_kernel_spec(kernel_spec.spec, launch_info.config, launch_info.connection_file, spawn_options);
}
exports.launch_jupyter_kernel = launch_jupyter_kernel;
//# sourceMappingURL=launch_jupyter_kernel.js.map