fastled 1.1.6__py2.py3-none-any.whl → 1.1.15__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastled/__init__.py +3 -0
- fastled/app.py +133 -105
- fastled/compile_server.py +221 -264
- fastled/docker_manager.py +3 -1
- fastled/filewatcher.py +196 -146
- fastled/keyboard.py +91 -0
- fastled/open_browser.py +5 -1
- fastled/sketch.py +37 -0
- fastled/util.py +10 -0
- fastled/web_compile.py +299 -227
- fastled-1.1.15.dist-info/METADATA +195 -0
- fastled-1.1.15.dist-info/RECORD +20 -0
- fastled/check_cpp_syntax.py +0 -34
- fastled-1.1.6.dist-info/METADATA +0 -118
- fastled-1.1.6.dist-info/RECORD +0 -19
- {fastled-1.1.6.dist-info → fastled-1.1.15.dist-info}/LICENSE +0 -0
- {fastled-1.1.6.dist-info → fastled-1.1.15.dist-info}/WHEEL +0 -0
- {fastled-1.1.6.dist-info → fastled-1.1.15.dist-info}/entry_points.txt +0 -0
- {fastled-1.1.6.dist-info → fastled-1.1.15.dist-info}/top_level.txt +0 -0
fastled/compile_server.py
CHANGED
@@ -1,264 +1,221 @@
|
|
1
|
-
import socket
|
2
|
-
import subprocess
|
3
|
-
import
|
4
|
-
import
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
import
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
port
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
self.
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
except
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
print("
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
if self.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
)
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
if
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
f"Server stopped with return code {self.running_process.returncode}"
|
223
|
-
)
|
224
|
-
|
225
|
-
except subprocess.TimeoutExpired:
|
226
|
-
# Force kill if it doesn't stop gracefully
|
227
|
-
if self.running_process:
|
228
|
-
self.running_process.kill()
|
229
|
-
self.running_process.wait()
|
230
|
-
except KeyboardInterrupt:
|
231
|
-
if self.running_process:
|
232
|
-
self.running_process.kill()
|
233
|
-
self.running_process.wait()
|
234
|
-
except Exception as e:
|
235
|
-
print(f"Error stopping Docker container: {e}")
|
236
|
-
finally:
|
237
|
-
self.running_process = None
|
238
|
-
# Signal the server thread to stop
|
239
|
-
self.running = False
|
240
|
-
if self.thread:
|
241
|
-
self.thread.join(timeout=10) # Wait up to 10 seconds for thread to finish
|
242
|
-
if self.thread.is_alive():
|
243
|
-
print("Warning: Server thread did not terminate properly")
|
244
|
-
|
245
|
-
print("Compile server stopped")
|
246
|
-
|
247
|
-
def _server_loop(self) -> None:
|
248
|
-
try:
|
249
|
-
while self.running:
|
250
|
-
if self.running_process:
|
251
|
-
# Read Docker container output
|
252
|
-
# Check if Docker process is still running
|
253
|
-
if self.running_process.poll() is not None:
|
254
|
-
print("Docker server stopped unexpectedly")
|
255
|
-
self.running = False
|
256
|
-
break
|
257
|
-
|
258
|
-
time.sleep(0.1) # Prevent busy waiting
|
259
|
-
except KeyboardInterrupt:
|
260
|
-
print("Server thread stopped by user.")
|
261
|
-
except Exception as e:
|
262
|
-
print(f"Error in server thread: {e}")
|
263
|
-
finally:
|
264
|
-
self.running = False
|
1
|
+
import socket
|
2
|
+
import subprocess
|
3
|
+
import time
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import httpx
|
7
|
+
|
8
|
+
from fastled.docker_manager import DockerManager
|
9
|
+
from fastled.sketch import looks_like_fastled_repo
|
10
|
+
|
11
|
+
_DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
|
12
|
+
|
13
|
+
SERVER_PORT = 9021
|
14
|
+
|
15
|
+
SERVER_OPTIONS = ["--allow-shutdown", "--no-auto-update"]
|
16
|
+
|
17
|
+
|
18
|
+
def find_available_port(start_port: int = SERVER_PORT) -> int:
|
19
|
+
"""Find an available port starting from the given port."""
|
20
|
+
port = start_port
|
21
|
+
end_port = start_port + 1000
|
22
|
+
while True:
|
23
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
24
|
+
if s.connect_ex(("localhost", port)) != 0:
|
25
|
+
return port
|
26
|
+
port += 1
|
27
|
+
if port >= end_port:
|
28
|
+
raise RuntimeError("No available ports found")
|
29
|
+
|
30
|
+
|
31
|
+
class CompileServer:
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
container_name=_DEFAULT_CONTAINER_NAME,
|
35
|
+
interactive: bool = False,
|
36
|
+
) -> None:
|
37
|
+
|
38
|
+
cwd = Path(".").resolve()
|
39
|
+
fastled_src_dir: Path | None = None
|
40
|
+
if looks_like_fastled_repo(cwd):
|
41
|
+
print(
|
42
|
+
"Looks like a FastLED repo, using it as the source directory and mapping it into the server."
|
43
|
+
)
|
44
|
+
fastled_src_dir = cwd / "src"
|
45
|
+
|
46
|
+
self.container_name = container_name
|
47
|
+
self.docker = DockerManager(container_name=container_name)
|
48
|
+
self.running = False
|
49
|
+
self.running_process: subprocess.Popen | None = None
|
50
|
+
self.fastled_src_dir: Path | None = fastled_src_dir
|
51
|
+
self.interactive = interactive
|
52
|
+
self._port = self._start()
|
53
|
+
# fancy print
|
54
|
+
if not interactive:
|
55
|
+
msg = f"# FastLED Compile Server started at {self.url()} #"
|
56
|
+
print("\n" + "#" * len(msg))
|
57
|
+
print(msg)
|
58
|
+
print("#" * len(msg) + "\n")
|
59
|
+
|
60
|
+
def using_fastled_src_dir_volume(self) -> bool:
|
61
|
+
return self.fastled_src_dir is not None
|
62
|
+
|
63
|
+
def port(self) -> int:
|
64
|
+
return self._port
|
65
|
+
|
66
|
+
def url(self) -> str:
|
67
|
+
return f"http://localhost:{self._port}"
|
68
|
+
|
69
|
+
def wait_for_startup(self, timeout: int = 100) -> bool:
|
70
|
+
"""Wait for the server to start up."""
|
71
|
+
start_time = time.time()
|
72
|
+
while time.time() - start_time < timeout:
|
73
|
+
# ping the server to see if it's up
|
74
|
+
if not self._port:
|
75
|
+
return False
|
76
|
+
# use httpx to ping the server
|
77
|
+
# if successful, return True
|
78
|
+
try:
|
79
|
+
response = httpx.get(
|
80
|
+
f"http://localhost:{self._port}", follow_redirects=True
|
81
|
+
)
|
82
|
+
if response.status_code < 400:
|
83
|
+
return True
|
84
|
+
except KeyboardInterrupt:
|
85
|
+
raise
|
86
|
+
except Exception:
|
87
|
+
pass
|
88
|
+
time.sleep(0.1)
|
89
|
+
if not self.running:
|
90
|
+
return False
|
91
|
+
return False
|
92
|
+
|
93
|
+
def _start(self) -> int:
|
94
|
+
print("Compiling server starting")
|
95
|
+
self.running = True
|
96
|
+
# Ensure Docker is running
|
97
|
+
with self.docker.get_lock():
|
98
|
+
if not self.docker.is_running():
|
99
|
+
if not self.docker.start():
|
100
|
+
print("Docker could not be started. Exiting.")
|
101
|
+
raise RuntimeError("Docker could not be started. Exiting.")
|
102
|
+
|
103
|
+
# Clean up any existing container with the same name
|
104
|
+
try:
|
105
|
+
container_exists = (
|
106
|
+
subprocess.run(
|
107
|
+
["docker", "inspect", self.container_name],
|
108
|
+
capture_output=True,
|
109
|
+
text=True,
|
110
|
+
).returncode
|
111
|
+
== 0
|
112
|
+
)
|
113
|
+
if container_exists:
|
114
|
+
print("Cleaning up existing container")
|
115
|
+
subprocess.run(
|
116
|
+
["docker", "rm", "-f", self.container_name],
|
117
|
+
check=False,
|
118
|
+
)
|
119
|
+
except KeyboardInterrupt:
|
120
|
+
raise
|
121
|
+
except Exception as e:
|
122
|
+
print(f"Warning: Failed to remove existing container: {e}")
|
123
|
+
|
124
|
+
print("Ensuring Docker image exists at latest version")
|
125
|
+
if not self.docker.ensure_image_exists():
|
126
|
+
print("Failed to ensure Docker image exists.")
|
127
|
+
raise RuntimeError("Failed to ensure Docker image exists")
|
128
|
+
|
129
|
+
print("Docker image now validated")
|
130
|
+
port = find_available_port()
|
131
|
+
print(f"Found an available port: {port}")
|
132
|
+
if self.interactive:
|
133
|
+
server_command = ["/bin/bash"]
|
134
|
+
else:
|
135
|
+
server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
|
136
|
+
server_cmd_str = subprocess.list2cmdline(server_command)
|
137
|
+
print(f"Started Docker container with command: {server_cmd_str}")
|
138
|
+
ports = {port: 80}
|
139
|
+
volumes = None
|
140
|
+
if self.fastled_src_dir:
|
141
|
+
print(
|
142
|
+
f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
|
143
|
+
)
|
144
|
+
volumes = {
|
145
|
+
str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
|
146
|
+
}
|
147
|
+
self.running_process = self.docker.run_container(
|
148
|
+
server_command, ports=ports, volumes=volumes, tty=self.interactive
|
149
|
+
)
|
150
|
+
print("Compile server starting")
|
151
|
+
time.sleep(3)
|
152
|
+
if self.running_process.poll() is not None:
|
153
|
+
print("Server failed to start")
|
154
|
+
self.running = False
|
155
|
+
raise RuntimeError("Server failed to start")
|
156
|
+
return port
|
157
|
+
|
158
|
+
def proceess_running(self) -> bool:
|
159
|
+
if self.running_process is None:
|
160
|
+
return False
|
161
|
+
return self.running_process.poll() is None
|
162
|
+
|
163
|
+
def stop(self) -> None:
|
164
|
+
print(f"Stopping server on port {self._port}")
|
165
|
+
# # attempt to send a shutdown signal to the server
|
166
|
+
# try:
|
167
|
+
# httpx.get(f"http://localhost:{self._port}/shutdown", timeout=2)
|
168
|
+
# # except Exception:
|
169
|
+
# except Exception as e:
|
170
|
+
# print(f"Failed to send shutdown signal: {e}")
|
171
|
+
# pass
|
172
|
+
try:
|
173
|
+
# Stop the Docker container
|
174
|
+
cp: subprocess.CompletedProcess
|
175
|
+
cp = subprocess.run(
|
176
|
+
["docker", "stop", self.container_name],
|
177
|
+
capture_output=True,
|
178
|
+
text=True,
|
179
|
+
check=True,
|
180
|
+
)
|
181
|
+
if cp.returncode != 0:
|
182
|
+
print(f"Failed to stop Docker container: {cp.stderr}")
|
183
|
+
|
184
|
+
cp = subprocess.run(
|
185
|
+
["docker", "rm", self.container_name],
|
186
|
+
capture_output=True,
|
187
|
+
text=True,
|
188
|
+
check=True,
|
189
|
+
)
|
190
|
+
if cp.returncode != 0:
|
191
|
+
print(f"Failed to remove Docker container: {cp.stderr}")
|
192
|
+
|
193
|
+
# Close the stdout pipe
|
194
|
+
if self.running_process and self.running_process.stdout:
|
195
|
+
self.running_process.stdout.close()
|
196
|
+
|
197
|
+
# Wait for the process to fully terminate with a timeout
|
198
|
+
self.running_process.wait(timeout=10)
|
199
|
+
if self.running_process.returncode is None:
|
200
|
+
# kill
|
201
|
+
self.running_process.kill()
|
202
|
+
if self.running_process.returncode is not None:
|
203
|
+
print(
|
204
|
+
f"Server stopped with return code {self.running_process.returncode}"
|
205
|
+
)
|
206
|
+
|
207
|
+
except subprocess.TimeoutExpired:
|
208
|
+
# Force kill if it doesn't stop gracefully
|
209
|
+
if self.running_process:
|
210
|
+
self.running_process.kill()
|
211
|
+
self.running_process.wait()
|
212
|
+
except KeyboardInterrupt:
|
213
|
+
if self.running_process:
|
214
|
+
self.running_process.kill()
|
215
|
+
self.running_process.wait()
|
216
|
+
except Exception as e:
|
217
|
+
print(f"Error stopping Docker container: {e}")
|
218
|
+
finally:
|
219
|
+
self.running_process = None
|
220
|
+
self.running = False
|
221
|
+
print("Compile server stopped")
|
fastled/docker_manager.py
CHANGED
@@ -212,6 +212,7 @@ class DockerManager:
|
|
212
212
|
cmd: list[str],
|
213
213
|
volumes: dict[str, dict[str, str]] | None = None,
|
214
214
|
ports: dict[int, int] | None = None,
|
215
|
+
tty: bool = False,
|
215
216
|
) -> subprocess.Popen:
|
216
217
|
"""Run the Docker container with the specified volume.
|
217
218
|
|
@@ -226,7 +227,8 @@ class DockerManager:
|
|
226
227
|
print("Creating new container...")
|
227
228
|
docker_command = ["docker", "run"]
|
228
229
|
|
229
|
-
if
|
230
|
+
if tty:
|
231
|
+
assert sys.stdout.isatty(), "TTY mode requires a TTY"
|
230
232
|
docker_command.append("-it")
|
231
233
|
# Attach volumes if specified
|
232
234
|
docker_command += [
|