diff --git a/Makefile b/Makefile index 8db16bb..dd078c4 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ pkg-clean: pkg-build: @: $${L} $${T} - cd $(BUILD)/src && pkg="$(PWD)/$(BUILD)/pkg" $(or $(CMD),../build.bash) + cd $(BUILD)/src && pkg="$(PWD)/$(BUILD)/pkg" src="$(PWD)/$(BUILD)/src" $(or $(CMD),../build.bash) pkg-debug: @: $${L} $${T} @@ -148,7 +148,8 @@ test: node backend/test-runner.js $(F) sandbox: - node backend/sandbox.js + @: $${L} + L=$(L) node backend/sandbox.js ### Fetch artifacts from registries diff --git a/backend/sandbox.bash b/backend/sandbox.bash new file mode 100644 index 0000000..051216f --- /dev/null +++ b/backend/sandbox.bash @@ -0,0 +1,61 @@ +# This script is sourced by Bash within 'make sandbox'. + +if [[ -z "$L" ]]; then + echo 'environment variable unset: $L' >&2 + exit 1 +fi + +cfg="$(< "/opt/riju/langs/$L.json")" || exit 1 + +function get { + jq -r ".$1" <<< "${cfg}" +} + +function has { + get "$@" | grep -vq '^null$' +} + +function daemon { + has daemon && eval "$(get daemon)" +} + +function setup { + has setup && eval "$(get setup)" +} + +function repl { + has repl && eval "$(get repl)" +} + +function main { + : > "$(get main)" + has prefix && get prefix >> "$(get main)" + get template >> "$(get main)" + has suffix && get suffix >> "$(get main)" +} + +function compile { + has compile && echo "$(get compile)" && eval "$(get compile)" +} + +function run-only { + has run && echo "$(get run)" && eval "$(get run)" +} + +function run { + compile && run-only +} + +function format { + has format && echo "$(get format.run)" && eval "( $(get format.run) ) < $(get main)" +} + +function lsp { + has lsp.setup && echo "$(get lsp.setup)" && eval "$(get lsp.setup)" + has lsp && echo "$(get lsp.start)" && eval "$(get lsp.start)" +} + +if [[ -z "$NS" ]]; then + main + setup +fi diff --git a/backend/sandbox.js b/backend/sandbox.js index 30a7e25..4dddd9b 100644 --- a/backend/sandbox.js +++ b/backend/sandbox.js @@ -1,6 +1,8 @@ import { spawn } from "child_process"; import { promises as fs } from "fs"; +import process from "process"; +import { quote } from "shell-quote"; import { v4 as getUUID } from "uuid"; import { borrowUser } from "./users.js"; @@ -21,10 +23,19 @@ function log(msg) { } async function main() { + const sandboxScript = await fs.readFile("backend/sandbox.bash", "utf-8"); + const lang = process.env.L; + if (!lang) { + die("environment variable unset: $L"); + } const uuid = getUUID(); const { uid, returnUser } = await borrowUser(log); await run(privilegedSetup({ uid, uuid }), log); - const args = privilegedSpawn({ uid, uuid }, ["bash"]); + const args = privilegedSpawn({ uid, uuid }, [ + "bash", + "-c", + `exec env L='${lang}' bash --rcfile <(cat <<< ${quote([sandboxScript])})`, + ]); const proc = spawn(args[0], args.slice(1), { stdio: "inherit", }); diff --git a/backend/users.js b/backend/users.js index 1abc66e..2f9af6f 100644 --- a/backend/users.js +++ b/backend/users.js @@ -37,10 +37,19 @@ async function getCreatedUsers() { } async function getActiveUsers() { + let dirents; + try { + dirents = await fs.readdir("/tmp/riju"); + } catch (err) { + if (err.code === "ENOENT") { + return new Set(); + } + throw err; + } return new Set( ( await Promise.all( - (await fs.readdir("/tmp/riju")) + dirents .filter((name) => name.match(uuidRegexp)) .map((name) => fs.stat(`/tmp/riju/${name}`)) ) diff --git a/docker/packaging/install.bash b/docker/packaging/install.bash index 2f11083..13d1357 100755 --- a/docker/packaging/install.bash +++ b/docker/packaging/install.bash @@ -34,6 +34,7 @@ ripgrep sudo tmux unzip +vim wget yarn diff --git a/deps/prettier.yaml b/shared/prettier.yaml similarity index 100% rename from deps/prettier.yaml rename to shared/prettier.yaml diff --git a/deps/sass.yaml b/shared/sass.yaml similarity index 100% rename from deps/sass.yaml rename to shared/sass.yaml diff --git a/tools/config.js b/tools/config.js index 9f562fc..7eca441 100644 --- a/tools/config.js +++ b/tools/config.js @@ -20,6 +20,14 @@ export async function getLangs() { .map((lang) => path.parse(lang).name); } +// Return a list of the IDs of all the configured shared dependencies. +// Each such ID can be passed to readSharedDepConfig. +export async function getSharedDeps() { + return (await fs.readdir("shared")) + .filter((lang) => lang.endsWith(".yaml")) + .map((lang) => path.parse(lang).name); +} + // Return a list of objects representing the packages to be built. See // the function implementation for the full list of keys. export async function getPackages() { @@ -36,6 +44,17 @@ export async function getPackages() { }); } } + for (const dep of await getSharedDeps()) { + const type = "shared"; + const name = `riju-${type}-${lang}`; + packages.push({ + lang, + type, + name, + buildScriptPath: `build/${type}/${lang}/build.bash`, + debPath: `build/${type}/${lang}/${name}.deb`, + }); + } return packages; } @@ -71,3 +90,17 @@ export async function readLangConfig(lang) { } return fixupLangConfig(langConfig); } + +// Read the YAML config file for the shared dependency with the given +// string ID and return it as an object. +export async function readSharedDepConfig(lang) { + const langConfig = YAML.parse( + await fs.readFile(`shared/${lang}.yaml`, "utf-8") + ); + if (langConfig.id !== lang) { + throw new Error( + `shared dependency config id ${langConfig.id} doesn't match expected ${lang}` + ); + } + return fixupLangConfig(langConfig); +} diff --git a/tools/generate-build-script.js b/tools/generate-build-script.js index 39db38e..5c05e36 100644 --- a/tools/generate-build-script.js +++ b/tools/generate-build-script.js @@ -3,18 +3,22 @@ import process from "process"; import { Command } from "commander"; import YAML from "yaml"; -import { readLangConfig } from "./config.js"; +import { readLangConfig, readSharedDepConfig } from "./config.js"; // Given a language config object, return the text of a Bash script // that will build the (unpacked) riju-lang-foo Debian package into // ${pkg} when run in an appropriate environment. This is a package // that will install the language interpreter/compiler and associated // tools. -function makeLangScript(langConfig) { +// +// isShared (optional) truthy means to generate a shared dependency +// package riju-shared-foo rather than a language installation +// package. +function makeLangScript(langConfig, isShared) { const { id, name, - install: { prepare, apt, pip, manual, deb }, + install: { prepare, apt, riju, pip, manual, deb }, } = langConfig; let parts = []; parts.push(`\ @@ -45,6 +49,9 @@ sudo apt-get install -y ${apt.join(" ")}`); if (apt) { depends = depends.concat(apt); } + if (riju) { + depends = depends.concat(riju.map((name) => `riju-shared-${name}`)); + } if (deb) { depends = depends.concat( deb.map((fname) => `\$(dpkg-deb -f "${fname}" Depends)`) @@ -52,11 +59,13 @@ sudo apt-get install -y ${apt.join(" ")}`); } parts.push(`depends=(${depends.map((dep) => `"${dep}"`).join(" ")})`); let debianControlData = `\ -Package: riju-lang-${id} +Package: riju-${isShared ? "shared" : "lang"}-${id} Version: \$(date +%s%3N) Architecture: amd64 Maintainer: Radon Rosborough -Description: The ${name} language packaged for Riju +Description: The ${name} ${ + isShared ? "shared dependency" : "language" + } packaged for Riju Depends: \$(IFS=,; echo "\${depends[*]}") Riju-Script-Hash: \$(sha1sum "\$0" | awk '{ print \$1 }')`; parts.push(`\ @@ -107,7 +116,7 @@ EOF`); // that installs tools used by multiple languages, and can be declared // as a dependency. function makeSharedScript(langConfig) { - throw new Error("shared script generation not implemented yet"); + return makeLangScript(langConfig, true); } // Parse command-line arguments, run main functionality, and exit. @@ -129,7 +138,13 @@ async function main() { console.error(`make-script.js: unsupported --type ${program.type}`); process.exit(1); } - console.log(scriptMaker(await readLangConfig(program.lang))); + console.log( + scriptMaker( + program.type === "shared" + ? await readSharedDepConfig(program.lang) + : await readLangConfig(program.lang) + ) + ); process.exit(0); }