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