Skip to content

Commit eb11e30

Browse files
committed
Automatically install Node via pnpm self-install
Processes via ProcessBuilder need to run inside an interactive shell to access necessary env variables; how to make that OS agnostic?
1 parent a8b9b2f commit eb11e30

File tree

5 files changed

+144
-18
lines changed

5 files changed

+144
-18
lines changed

p5js/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"electron": "^37.4.0"
77
},
88
"dependencies": {
9-
"p5": "^2.0.5",
9+
"p5": "^1.11.10",
1010
"p5.sound": "^0.2.0"
1111
}
1212
}

p5js/js/renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const sendToMainHandler = {
2121
console = new Proxy(console, sendToMainHandler);
2222

2323
// TODO: move error handling logic to Kotlin
24+
// TODO: Let p5.js FES do the error parsing?
2425
addEventListener("error", ({ message, filename, lineno, colno}) => {
2526
pde.sendMessage(
2627
["error", message, path.basename(filename), lineno, colno].join("|")

p5js/library/install.sh

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/bin/sh
2+
3+
# From https://github.com/Homebrew/install/blob/master/install.sh
4+
abort() {
5+
printf "%s\n" "$@"
6+
exit 1
7+
}
8+
9+
# string formatters
10+
if [ -t 1 ]; then
11+
tty_escape() { printf "\033[%sm" "$1"; }
12+
else
13+
tty_escape() { :; }
14+
fi
15+
tty_mkbold() { tty_escape "1;$1"; }
16+
tty_blue="$(tty_mkbold 34)"
17+
tty_bold="$(tty_mkbold 39)"
18+
tty_reset="$(tty_escape 0)"
19+
20+
ohai() {
21+
printf "${tty_blue}==>${tty_bold} %s${tty_reset}\n" "$1"
22+
}
23+
24+
# End from https://github.com/Homebrew/install/blob/master/install.sh
25+
26+
download() {
27+
if command -v curl > /dev/null 2>&1; then
28+
curl -fsSL "$1"
29+
else
30+
wget -qO- "$1"
31+
fi
32+
}
33+
34+
is_glibc_compatible() {
35+
getconf GNU_LIBC_VERSION >/dev/null 2>&1 || ldd --version >/dev/null 2>&1 || return 1
36+
}
37+
38+
detect_platform() {
39+
local platform
40+
platform="$(uname -s | tr '[:upper:]' '[:lower:]')"
41+
42+
case "${platform}" in
43+
linux)
44+
if is_glibc_compatible; then
45+
platform="linux"
46+
else
47+
platform="linuxstatic"
48+
fi
49+
;;
50+
darwin) platform="macos" ;;
51+
windows) platform="win" ;;
52+
mingw*) platform="win" ;;
53+
esac
54+
55+
printf '%s' "${platform}"
56+
}
57+
58+
detect_arch() {
59+
local arch
60+
arch="$(uname -m | tr '[:upper:]' '[:lower:]')"
61+
62+
case "${arch}" in
63+
x86_64 | amd64) arch="x64" ;;
64+
armv*) arch="arm" ;;
65+
arm64 | aarch64) arch="arm64" ;;
66+
esac
67+
68+
# `uname -m` in some cases mis-reports 32-bit OS as 64-bit, so double check
69+
if [ "${arch}" = "x64" ] && [ "$(getconf LONG_BIT)" -eq 32 ]; then
70+
arch=i686
71+
elif [ "${arch}" = "arm64" ] && [ "$(getconf LONG_BIT)" -eq 32 ]; then
72+
arch=arm
73+
fi
74+
75+
case "$arch" in
76+
x64*) ;;
77+
arm64*) ;;
78+
*) return 1
79+
esac
80+
printf '%s' "${arch}"
81+
}
82+
83+
download_and_install() {
84+
local platform arch version_json version archive_url tmp_dir
85+
platform="$(detect_platform)"
86+
arch="$(detect_arch)" || abort "Sorry! pnpm currently only provides pre-built binaries for x86_64/arm64 architectures."
87+
if [ -z "${PNPM_VERSION}" ]; then
88+
version_json="$(download "https://registry.npmjs.org/@pnpm/exe")" || abort "Download Error!"
89+
version="$(echo "$version_json" | grep -o '"latest":[[:space:]]*"[0-9.]*"' | grep -o '[0-9.]*')"
90+
else
91+
version="${PNPM_VERSION}"
92+
fi
93+
94+
archive_url="https://github.com/pnpm/pnpm/releases/download/v${version}/pnpm-${platform}-${arch}"
95+
if [ "${platform}" = "win" ]; then
96+
archive_url="${archive_url}.exe"
97+
fi
98+
99+
# install to PNPM_HOME, defaulting to ~/.pnpm
100+
tmp_dir="$(mktemp -d)" || abort "Tmpdir Error!"
101+
trap 'rm -rf "$tmp_dir"' EXIT INT TERM HUP
102+
103+
ohai "Downloading pnpm binaries ${version}"
104+
# download the binary to the specified directory
105+
download "$archive_url" > "$tmp_dir/pnpm" || return 1
106+
chmod +x "$tmp_dir/pnpm"
107+
SHELL="$SHELL" "$tmp_dir/pnpm" setup --force || return 1
108+
}
109+
110+
download_and_install || abort "Install Error!"

p5js/src/main/kotlin/p5jsEditor.kt

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import processing.app.ui.EditorToolbar
3434
import processing.app.ui.theme.ProcessingTheme
3535
import java.io.BufferedReader
3636
import java.io.File
37+
import java.io.IOException
3738
import java.io.InputStreamReader
3839
import java.net.URL
3940
import javax.swing.JMenu
@@ -42,6 +43,7 @@ import javax.swing.JMenu
4243
class p5jsEditor(base: Base, path: String?, state: EditorState?, mode: Mode?): Editor(base, path, state, mode) {
4344

4445
val scope = CoroutineScope(Dispatchers.Default)
46+
4547
init {
4648
scope.launch {
4749
val folder = sketch.folder
@@ -71,18 +73,33 @@ class p5jsEditor(base: Base, path: String?, state: EditorState?, mode: Mode?): E
7173
7274
<body>
7375
<script src="renderer.js"></script>
74-
<script src="./node_modules/p5/lib/p5.min.js"></script>
75-
<script src="./node_modules/p5.sound/dist/p5.sound.min.js"></script>
76+
<script src="./node_modules/p5/lib/p5.js"></script>
77+
<script src="./node_modules/p5.sound/dist/p5.sound.js"></script>
7678
<script src="$name.js"></script>
7779
</body>
7880
</html>
7981
""".trimIndent()
8082
File("$folder/index.html").writeText(indexHtml)
8183

82-
// TODO: Install `pnpm` automatically, stand-alone, and use as Node manager
83-
runNpmActions(folder, TYPE.npm, listOf("install", "-g", "pnpm"))
84+
// TODO: refactor into functions
85+
// Check whether `pnpm` is already installed; horrible code—my apologies!
86+
// TODO: Make more robust, cross-platform, etc. Only job for now is to get a PDEX file out that works on MacOS
87+
statusNotice("Looking for pnpm…")
88+
try {
89+
// TODO: Only an interactive shell allows me access to pnpm
90+
runCommand("/bin/bash", listOf("-ci", "pnpm -v"))
91+
}
92+
catch (e: Exception) {
93+
statusNotice("pnpm not found. Installing pnpm…")
94+
runCommand("/bin/bash", listOf("-ci", "${mode?.folder}/install.sh"))
95+
96+
statusNotice("Installing Node via pnpm…")
97+
runCommand("/bin/bash", listOf("-ci", "pnpm env use --global lts"))
98+
}
99+
100+
statusNotice("")
84101
// --dangerously-allow-all-builds allows electron in particular to install properly
85-
runNpmActions(folder, TYPE.pnpm, listOf("install", "--dangerously-allow-all-builds"))
102+
runCommand("/bin/bash", listOf("-ci", "pnpm install --dangerously-allow-all-builds"))
86103
}
87104
}
88105

@@ -126,9 +143,9 @@ class p5jsEditor(base: Base, path: String?, state: EditorState?, mode: Mode?): E
126143
processes.forEach { it.destroy() }
127144
}
128145

129-
130146
override fun deactivateRun() {
131147
processes.forEach { it.destroy() }
148+
toolbar.deactivateRun()
132149
}
133150

134151
override fun createFooter(): EditorFooter {
@@ -170,7 +187,7 @@ class p5jsEditor(base: Base, path: String?, state: EditorState?, mode: Mode?): E
170187
Button(onClick = {
171188
if (packageToInstall.isNotBlank()) {
172189
// TODO Better error handling
173-
runNpmActions(sketch.folder, TYPE.pnpm, listOf("add", packageToInstall, "--dangerously-allow-all-builds"))
190+
runCommand("pnpm", listOf("add", packageToInstall, "--dangerously-allow-all-builds"))
174191
packageToInstall = ""
175192
}
176193
}) {
@@ -192,25 +209,22 @@ class p5jsEditor(base: Base, path: String?, state: EditorState?, mode: Mode?): E
192209
return footer
193210
}
194211

195-
enum class TYPE{
196-
npm, pnpm, npx
197-
}
198-
199212
private fun filenameToCodeIndex(filename: String) {
200213

201214
}
202215

216+
// TODO: state is maintained => turn into class
203217
val processes = mutableListOf<Process>()
204-
fun runNpmActions(directory: File, type: TYPE, actions: List<String>, onFinished: () -> Unit = {}) {
218+
fun runCommand(type: String, actions: List<String>, directory: File = sketch.folder, onFinished: () -> Unit = {}) {
205219
// Wait for previous processes to finish
206220
processes.forEach { it.waitFor() }
207221

208222
val processBuilder = ProcessBuilder()
209223
// Set the command based on the operating system
210224
val command = if (System.getProperty("os.name").lowercase().contains("windows")) {
211-
listOf("cmd", "/c", type.name , *actions.toTypedArray())
225+
listOf("cmd", "/c", type, *actions.toTypedArray())
212226
} else {
213-
listOf(type.name, *actions.toTypedArray())
227+
listOf(type, *actions.toTypedArray())
214228
}
215229

216230
processBuilder.command(command)
@@ -227,7 +241,7 @@ class p5jsEditor(base: Base, path: String?, state: EditorState?, mode: Mode?): E
227241
while (reader.readLine().also { line = it } != null) {
228242
// TODO: so much refactoring!
229243
// Only check for errors when running the sketch
230-
if (type == TYPE.npx && line.startsWith("error")) {
244+
if (actions[1].startsWith("npx") && line.startsWith("error")) {
231245
// TODO: more robust data exchange, double-check with @Stef
232246
// TODO: `statusError` does not do anything with column of a SketchException
233247
val ( msgType, msgText, msgFile, msgLine, msgCol ) = line.split("|")

p5js/src/main/kotlin/p5jsEditorToolbar.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package processing.p5js
22

33
import kotlinx.coroutines.launch
4+
import processing.app.Messages
45
import processing.app.ui.Editor
56
import processing.app.ui.EditorToolbar
67

@@ -12,15 +13,15 @@ class p5jsEditorToolbar(editor: p5jsEditor?) : EditorToolbar(editor) {
1213
editor.sketch.save()
1314

1415
runButton.setSelected(true)
15-
editor.runNpmActions(editor.sketch.folder, p5jsEditor.TYPE.npx, listOf("electron", ".")){
16+
editor.runCommand("/bin/bash", listOf("-ci", "npx electron .")) {
1617
runButton.setSelected(false)
1718
}
18-
1919
}
2020
}
2121

2222
override fun handleStop() {
2323
val editor = editor as p5jsEditor
2424
editor.processes.forEach { it.destroy() }
25+
deactivateRun()
2526
}
2627
}

0 commit comments

Comments
 (0)