fastled 1.2.50__py3-none-any.whl → 1.2.53__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 +1 -1
- fastled/app.py +1 -2
- fastled/cli_test.py +20 -0
- fastled/client_server.py +405 -405
- fastled/docker_manager.py +1 -0
- fastled/filewatcher.py +3 -1
- fastled/project_init.py +129 -129
- fastled/settings.py +1 -0
- fastled/site/build.py +458 -457
- {fastled-1.2.50.dist-info → fastled-1.2.53.dist-info}/METADATA +384 -384
- {fastled-1.2.50.dist-info → fastled-1.2.53.dist-info}/RECORD +15 -14
- {fastled-1.2.50.dist-info → fastled-1.2.53.dist-info}/WHEEL +1 -1
- {fastled-1.2.50.dist-info → fastled-1.2.53.dist-info}/entry_points.txt +0 -0
- {fastled-1.2.50.dist-info → fastled-1.2.53.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.2.50.dist-info → fastled-1.2.53.dist-info}/top_level.txt +0 -0
fastled/client_server.py
CHANGED
@@ -1,405 +1,405 @@
|
|
1
|
-
import shutil
|
2
|
-
import tempfile
|
3
|
-
import threading
|
4
|
-
import time
|
5
|
-
from multiprocessing import Process
|
6
|
-
from pathlib import Path
|
7
|
-
|
8
|
-
from fastled.compile_server import CompileServer
|
9
|
-
from fastled.docker_manager import DockerManager
|
10
|
-
from fastled.filewatcher import FileWatcherProcess
|
11
|
-
from fastled.keyboard import SpaceBarWatcher
|
12
|
-
from fastled.open_browser import open_browser_process
|
13
|
-
from fastled.parse_args import Args
|
14
|
-
from fastled.settings import DEFAULT_URL
|
15
|
-
from fastled.sketch import looks_like_sketch_directory
|
16
|
-
from fastled.types import BuildMode, CompileResult, CompileServerError
|
17
|
-
from fastled.web_compile import (
|
18
|
-
SERVER_PORT,
|
19
|
-
ConnectionResult,
|
20
|
-
find_good_connection,
|
21
|
-
web_compile,
|
22
|
-
)
|
23
|
-
|
24
|
-
|
25
|
-
def _create_error_html(error_message: str) -> str:
|
26
|
-
return f"""<!DOCTYPE html>
|
27
|
-
<html>
|
28
|
-
<head>
|
29
|
-
<!-- no cache -->
|
30
|
-
<meta http-equiv="Cache-Control" content="no-store" />
|
31
|
-
<meta http-equiv="Pragma" content="no-cache" />
|
32
|
-
<meta http-equiv="Expires" content="0" />
|
33
|
-
<title>FastLED Compilation Error ZACH</title>
|
34
|
-
<style>
|
35
|
-
body {{
|
36
|
-
background-color: #1a1a1a;
|
37
|
-
color: #ffffff;
|
38
|
-
font-family: Arial, sans-serif;
|
39
|
-
margin: 20px;
|
40
|
-
padding: 20px;
|
41
|
-
}}
|
42
|
-
pre {{
|
43
|
-
color: #ffffff;
|
44
|
-
background-color: #1a1a1a;
|
45
|
-
border: 1px solid #444444;
|
46
|
-
border-radius: 4px;
|
47
|
-
padding: 15px;
|
48
|
-
white-space: pre-wrap;
|
49
|
-
word-wrap: break-word;
|
50
|
-
}}
|
51
|
-
</style>
|
52
|
-
</head>
|
53
|
-
<body>
|
54
|
-
<h1>Compilation Failed</h1>
|
55
|
-
<pre>{error_message}</pre>
|
56
|
-
</body>
|
57
|
-
</html>"""
|
58
|
-
|
59
|
-
|
60
|
-
# Override this function in your own code to run tests before compilation
|
61
|
-
def TEST_BEFORE_COMPILE(url) -> None:
|
62
|
-
pass
|
63
|
-
|
64
|
-
|
65
|
-
def _run_web_compiler(
|
66
|
-
directory: Path,
|
67
|
-
host: str,
|
68
|
-
build_mode: BuildMode,
|
69
|
-
profile: bool,
|
70
|
-
last_hash_value: str | None,
|
71
|
-
) -> CompileResult:
|
72
|
-
input_dir = Path(directory)
|
73
|
-
output_dir = input_dir / "fastled_js"
|
74
|
-
start = time.time()
|
75
|
-
web_result = web_compile(
|
76
|
-
directory=input_dir, host=host, build_mode=build_mode, profile=profile
|
77
|
-
)
|
78
|
-
diff = time.time() - start
|
79
|
-
if not web_result.success:
|
80
|
-
print("\nWeb compilation failed:")
|
81
|
-
print(f"Time taken: {diff:.2f} seconds")
|
82
|
-
print(web_result.stdout)
|
83
|
-
# Create error page
|
84
|
-
output_dir.mkdir(exist_ok=True)
|
85
|
-
error_html = _create_error_html(web_result.stdout)
|
86
|
-
(output_dir / "index.html").write_text(error_html, encoding="utf-8")
|
87
|
-
return web_result
|
88
|
-
|
89
|
-
def print_results() -> None:
|
90
|
-
hash_value = (
|
91
|
-
web_result.hash_value
|
92
|
-
if web_result.hash_value is not None
|
93
|
-
else "NO HASH VALUE"
|
94
|
-
)
|
95
|
-
print(
|
96
|
-
f"\nWeb compilation successful\n Time: {diff:.2f}\n output: {output_dir}\n hash: {hash_value}\n zip size: {len(web_result.zip_bytes)} bytes"
|
97
|
-
)
|
98
|
-
|
99
|
-
# now check to see if the hash value is the same as the last hash value
|
100
|
-
if last_hash_value is not None and last_hash_value == web_result.hash_value:
|
101
|
-
print("\nSkipping redeploy: No significant changes found.")
|
102
|
-
print_results()
|
103
|
-
return web_result
|
104
|
-
|
105
|
-
# Extract zip contents to fastled_js directory
|
106
|
-
output_dir.mkdir(exist_ok=True)
|
107
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
108
|
-
temp_path = Path(temp_dir)
|
109
|
-
temp_zip = temp_path / "result.zip"
|
110
|
-
temp_zip.write_bytes(web_result.zip_bytes)
|
111
|
-
|
112
|
-
# Clear existing contents
|
113
|
-
shutil.rmtree(output_dir, ignore_errors=True)
|
114
|
-
output_dir.mkdir(exist_ok=True)
|
115
|
-
|
116
|
-
# Extract zip contents
|
117
|
-
shutil.unpack_archive(temp_zip, output_dir, "zip")
|
118
|
-
|
119
|
-
print(web_result.stdout)
|
120
|
-
print_results()
|
121
|
-
return web_result
|
122
|
-
|
123
|
-
|
124
|
-
def _try_start_server_or_get_url(
|
125
|
-
auto_update: bool, args_web: str | bool, localhost: bool
|
126
|
-
) -> tuple[str, CompileServer | None]:
|
127
|
-
is_local_host = localhost or (
|
128
|
-
isinstance(args_web, str)
|
129
|
-
and ("localhost" in args_web or "127.0.0.1" in args_web)
|
130
|
-
)
|
131
|
-
# test to see if there is already a local host server
|
132
|
-
local_host_needs_server = False
|
133
|
-
if is_local_host:
|
134
|
-
addr = "localhost" if localhost or not isinstance(args_web, str) else args_web
|
135
|
-
urls = [addr]
|
136
|
-
if ":" not in addr:
|
137
|
-
urls.append(f"{addr}:{SERVER_PORT}")
|
138
|
-
|
139
|
-
result: ConnectionResult | None = find_good_connection(urls)
|
140
|
-
if result is not None:
|
141
|
-
print(f"Found local server at {result.host}")
|
142
|
-
return (result.host, None)
|
143
|
-
else:
|
144
|
-
local_host_needs_server = True
|
145
|
-
|
146
|
-
if not local_host_needs_server and args_web:
|
147
|
-
if isinstance(args_web, str):
|
148
|
-
return (args_web, None)
|
149
|
-
if isinstance(args_web, bool):
|
150
|
-
return (DEFAULT_URL, None)
|
151
|
-
return (args_web, None)
|
152
|
-
else:
|
153
|
-
try:
|
154
|
-
print("No local server found, starting one...")
|
155
|
-
compile_server = CompileServer(auto_updates=auto_update)
|
156
|
-
print("Waiting for the local compiler to start...")
|
157
|
-
if not compile_server.ping():
|
158
|
-
print("Failed to start local compiler.")
|
159
|
-
raise CompileServerError("Failed to start local compiler.")
|
160
|
-
return (compile_server.url(), compile_server)
|
161
|
-
except KeyboardInterrupt:
|
162
|
-
raise
|
163
|
-
except RuntimeError:
|
164
|
-
print("Failed to start local compile server, using web compiler instead.")
|
165
|
-
return (DEFAULT_URL, None)
|
166
|
-
|
167
|
-
|
168
|
-
def run_client(
|
169
|
-
directory: Path,
|
170
|
-
host: str | CompileServer | None,
|
171
|
-
open_web_browser: bool = True,
|
172
|
-
keep_running: bool = True, # if false, only one compilation will be done.
|
173
|
-
build_mode: BuildMode = BuildMode.QUICK,
|
174
|
-
profile: bool = False,
|
175
|
-
shutdown: threading.Event | None = None,
|
176
|
-
) -> int:
|
177
|
-
|
178
|
-
compile_server: CompileServer | None = (
|
179
|
-
host if isinstance(host, CompileServer) else None
|
180
|
-
)
|
181
|
-
shutdown = shutdown or threading.Event()
|
182
|
-
|
183
|
-
def get_url() -> str:
|
184
|
-
if compile_server is not None:
|
185
|
-
return compile_server.url()
|
186
|
-
if isinstance(host, str):
|
187
|
-
return host
|
188
|
-
return DEFAULT_URL
|
189
|
-
|
190
|
-
url = get_url()
|
191
|
-
|
192
|
-
try:
|
193
|
-
|
194
|
-
def compile_function(
|
195
|
-
url: str = url,
|
196
|
-
build_mode: BuildMode = build_mode,
|
197
|
-
profile: bool = profile,
|
198
|
-
last_hash_value: str | None = None,
|
199
|
-
) -> CompileResult:
|
200
|
-
TEST_BEFORE_COMPILE(url)
|
201
|
-
return _run_web_compiler(
|
202
|
-
directory,
|
203
|
-
host=url,
|
204
|
-
build_mode=build_mode,
|
205
|
-
profile=profile,
|
206
|
-
last_hash_value=last_hash_value,
|
207
|
-
)
|
208
|
-
|
209
|
-
result: CompileResult = compile_function(last_hash_value=None)
|
210
|
-
last_compiled_result: CompileResult = result
|
211
|
-
|
212
|
-
if not result.success:
|
213
|
-
print("\nCompilation failed.")
|
214
|
-
|
215
|
-
browser_proc: Process | None = None
|
216
|
-
if open_web_browser:
|
217
|
-
browser_proc = open_browser_process(directory / "fastled_js")
|
218
|
-
else:
|
219
|
-
print("\nCompilation successful.")
|
220
|
-
if compile_server:
|
221
|
-
print("Shutting down compile server...")
|
222
|
-
compile_server.stop()
|
223
|
-
return 0
|
224
|
-
|
225
|
-
if not keep_running or shutdown.is_set():
|
226
|
-
if browser_proc:
|
227
|
-
browser_proc.kill()
|
228
|
-
return 0 if result.success else 1
|
229
|
-
except KeyboardInterrupt:
|
230
|
-
print("\nExiting from main")
|
231
|
-
return 1
|
232
|
-
|
233
|
-
excluded_patterns = ["fastled_js"]
|
234
|
-
|
235
|
-
sketch_filewatcher = FileWatcherProcess(
|
236
|
-
directory, excluded_patterns=excluded_patterns
|
237
|
-
)
|
238
|
-
|
239
|
-
source_code_watcher: FileWatcherProcess | None = None
|
240
|
-
if compile_server and compile_server.using_fastled_src_dir_volume():
|
241
|
-
assert compile_server.fastled_src_dir is not None
|
242
|
-
source_code_watcher = FileWatcherProcess(
|
243
|
-
compile_server.fastled_src_dir, excluded_patterns=excluded_patterns
|
244
|
-
)
|
245
|
-
|
246
|
-
def trigger_rebuild_if_sketch_changed(
|
247
|
-
last_compiled_result: CompileResult,
|
248
|
-
) -> tuple[bool, CompileResult]:
|
249
|
-
changed_files = sketch_filewatcher.get_all_changes()
|
250
|
-
if changed_files:
|
251
|
-
print(f"\nChanges detected in {changed_files}")
|
252
|
-
last_hash_value = last_compiled_result.hash_value
|
253
|
-
out = compile_function(last_hash_value=last_hash_value)
|
254
|
-
if not out.success:
|
255
|
-
print("\nRecompilation failed.")
|
256
|
-
else:
|
257
|
-
print("\nRecompilation successful.")
|
258
|
-
return True, out
|
259
|
-
return False, last_compiled_result
|
260
|
-
|
261
|
-
def print_status() -> None:
|
262
|
-
print("Will compile on sketch changes or if you hit the space bar.")
|
263
|
-
|
264
|
-
print_status()
|
265
|
-
print("Press Ctrl+C to stop...")
|
266
|
-
|
267
|
-
try:
|
268
|
-
while True:
|
269
|
-
if shutdown.is_set():
|
270
|
-
print("\nStopping watch mode...")
|
271
|
-
return 0
|
272
|
-
if SpaceBarWatcher.watch_space_bar_pressed(timeout=1.0):
|
273
|
-
print("Compiling...")
|
274
|
-
last_compiled_result = compile_function(last_hash_value=None)
|
275
|
-
if not last_compiled_result.success:
|
276
|
-
print("\nRecompilation failed.")
|
277
|
-
else:
|
278
|
-
print("\nRecompilation successful.")
|
279
|
-
# drain the space bar queue
|
280
|
-
SpaceBarWatcher.watch_space_bar_pressed()
|
281
|
-
print_status()
|
282
|
-
continue
|
283
|
-
changed, last_compiled_result = trigger_rebuild_if_sketch_changed(
|
284
|
-
last_compiled_result
|
285
|
-
)
|
286
|
-
if changed:
|
287
|
-
print_status()
|
288
|
-
continue
|
289
|
-
if compile_server and not compile_server.process_running():
|
290
|
-
print("Server process is not running. Exiting...")
|
291
|
-
return 1
|
292
|
-
if source_code_watcher is not None:
|
293
|
-
changed_files = source_code_watcher.get_all_changes()
|
294
|
-
# de-duplicate changes
|
295
|
-
changed_files = sorted(list(set(changed_files)))
|
296
|
-
if changed_files:
|
297
|
-
print(f"\nChanges detected in FastLED source code: {changed_files}")
|
298
|
-
print("Press space bar to trigger compile.")
|
299
|
-
while True:
|
300
|
-
space_bar_pressed = SpaceBarWatcher.watch_space_bar_pressed(
|
301
|
-
timeout=1.0
|
302
|
-
)
|
303
|
-
file_changes = source_code_watcher.get_all_changes()
|
304
|
-
sketch_files_changed = sketch_filewatcher.get_all_changes()
|
305
|
-
|
306
|
-
if file_changes:
|
307
|
-
print(
|
308
|
-
f"Changes detected in {file_changes}\nHit the space bar to trigger compile."
|
309
|
-
)
|
310
|
-
|
311
|
-
if space_bar_pressed or sketch_files_changed:
|
312
|
-
if space_bar_pressed:
|
313
|
-
print("Space bar pressed, triggering recompile...")
|
314
|
-
elif sketch_files_changed:
|
315
|
-
print(
|
316
|
-
f"Changes detected in {','.join(sketch_files_changed)}, triggering recompile..."
|
317
|
-
)
|
318
|
-
last_compiled_result = compile_function(
|
319
|
-
last_hash_value=None
|
320
|
-
)
|
321
|
-
print("Finished recompile.")
|
322
|
-
# Drain the space bar queue
|
323
|
-
SpaceBarWatcher.watch_space_bar_pressed()
|
324
|
-
print_status()
|
325
|
-
continue
|
326
|
-
|
327
|
-
except KeyboardInterrupt:
|
328
|
-
print("\nStopping watch mode...")
|
329
|
-
return 0
|
330
|
-
except Exception as e:
|
331
|
-
print(f"Error: {e}")
|
332
|
-
return 1
|
333
|
-
finally:
|
334
|
-
sketch_filewatcher.stop()
|
335
|
-
if compile_server:
|
336
|
-
compile_server.stop()
|
337
|
-
if browser_proc:
|
338
|
-
browser_proc.kill()
|
339
|
-
|
340
|
-
|
341
|
-
def run_client_server(args: Args) -> int:
|
342
|
-
profile = bool(args.profile)
|
343
|
-
web: str | bool = args.web if isinstance(args.web, str) else bool(args.web)
|
344
|
-
auto_update = bool(args.auto_update)
|
345
|
-
localhost = bool(args.localhost)
|
346
|
-
directory = args.directory if args.directory else Path(".")
|
347
|
-
just_compile = bool(args.just_compile)
|
348
|
-
interactive = bool(args.interactive)
|
349
|
-
force_compile = bool(args.force_compile)
|
350
|
-
open_web_browser = not just_compile and not interactive
|
351
|
-
build_mode: BuildMode = BuildMode.from_args(args)
|
352
|
-
|
353
|
-
if not force_compile and not looks_like_sketch_directory(directory):
|
354
|
-
# if there is only one directory in the sketch directory, use that
|
355
|
-
found_valid_child = False
|
356
|
-
if len(list(directory.iterdir())) == 1:
|
357
|
-
child_dir = next(directory.iterdir())
|
358
|
-
if looks_like_sketch_directory(child_dir):
|
359
|
-
found_valid_child = True
|
360
|
-
print(
|
361
|
-
f"The selected directory is not a valid FastLED sketch directory, the child directory {child_dir} looks like a sketch directory, using that instead."
|
362
|
-
)
|
363
|
-
directory = child_dir
|
364
|
-
if not found_valid_child:
|
365
|
-
print(
|
366
|
-
f"Error: {directory} is not a valid FastLED sketch directory, if you are sure it is, use --force-compile"
|
367
|
-
)
|
368
|
-
return 1
|
369
|
-
|
370
|
-
# If not explicitly using web compiler, check Docker installation
|
371
|
-
if not web and not DockerManager.is_docker_installed():
|
372
|
-
print(
|
373
|
-
"\nDocker is not installed on this system - switching to web compiler instead."
|
374
|
-
)
|
375
|
-
web = True
|
376
|
-
|
377
|
-
url: str
|
378
|
-
compile_server: CompileServer | None = None
|
379
|
-
try:
|
380
|
-
url, compile_server = _try_start_server_or_get_url(auto_update, web, localhost)
|
381
|
-
except KeyboardInterrupt:
|
382
|
-
print("\nExiting from first try...")
|
383
|
-
if compile_server:
|
384
|
-
compile_server.stop()
|
385
|
-
return 1
|
386
|
-
except Exception as e:
|
387
|
-
print(f"Error: {e}")
|
388
|
-
if compile_server:
|
389
|
-
compile_server.stop()
|
390
|
-
return 1
|
391
|
-
|
392
|
-
try:
|
393
|
-
return run_client(
|
394
|
-
directory=directory,
|
395
|
-
host=compile_server if compile_server else url,
|
396
|
-
open_web_browser=open_web_browser,
|
397
|
-
keep_running=not just_compile,
|
398
|
-
build_mode=build_mode,
|
399
|
-
profile=profile,
|
400
|
-
)
|
401
|
-
except KeyboardInterrupt:
|
402
|
-
return 1
|
403
|
-
finally:
|
404
|
-
if compile_server:
|
405
|
-
compile_server.stop()
|
1
|
+
import shutil
|
2
|
+
import tempfile
|
3
|
+
import threading
|
4
|
+
import time
|
5
|
+
from multiprocessing import Process
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from fastled.compile_server import CompileServer
|
9
|
+
from fastled.docker_manager import DockerManager
|
10
|
+
from fastled.filewatcher import FileWatcherProcess
|
11
|
+
from fastled.keyboard import SpaceBarWatcher
|
12
|
+
from fastled.open_browser import open_browser_process
|
13
|
+
from fastled.parse_args import Args
|
14
|
+
from fastled.settings import DEFAULT_URL
|
15
|
+
from fastled.sketch import looks_like_sketch_directory
|
16
|
+
from fastled.types import BuildMode, CompileResult, CompileServerError
|
17
|
+
from fastled.web_compile import (
|
18
|
+
SERVER_PORT,
|
19
|
+
ConnectionResult,
|
20
|
+
find_good_connection,
|
21
|
+
web_compile,
|
22
|
+
)
|
23
|
+
|
24
|
+
|
25
|
+
def _create_error_html(error_message: str) -> str:
|
26
|
+
return f"""<!DOCTYPE html>
|
27
|
+
<html>
|
28
|
+
<head>
|
29
|
+
<!-- no cache -->
|
30
|
+
<meta http-equiv="Cache-Control" content="no-store" />
|
31
|
+
<meta http-equiv="Pragma" content="no-cache" />
|
32
|
+
<meta http-equiv="Expires" content="0" />
|
33
|
+
<title>FastLED Compilation Error ZACH</title>
|
34
|
+
<style>
|
35
|
+
body {{
|
36
|
+
background-color: #1a1a1a;
|
37
|
+
color: #ffffff;
|
38
|
+
font-family: Arial, sans-serif;
|
39
|
+
margin: 20px;
|
40
|
+
padding: 20px;
|
41
|
+
}}
|
42
|
+
pre {{
|
43
|
+
color: #ffffff;
|
44
|
+
background-color: #1a1a1a;
|
45
|
+
border: 1px solid #444444;
|
46
|
+
border-radius: 4px;
|
47
|
+
padding: 15px;
|
48
|
+
white-space: pre-wrap;
|
49
|
+
word-wrap: break-word;
|
50
|
+
}}
|
51
|
+
</style>
|
52
|
+
</head>
|
53
|
+
<body>
|
54
|
+
<h1>Compilation Failed</h1>
|
55
|
+
<pre>{error_message}</pre>
|
56
|
+
</body>
|
57
|
+
</html>"""
|
58
|
+
|
59
|
+
|
60
|
+
# Override this function in your own code to run tests before compilation
|
61
|
+
def TEST_BEFORE_COMPILE(url) -> None:
|
62
|
+
pass
|
63
|
+
|
64
|
+
|
65
|
+
def _run_web_compiler(
|
66
|
+
directory: Path,
|
67
|
+
host: str,
|
68
|
+
build_mode: BuildMode,
|
69
|
+
profile: bool,
|
70
|
+
last_hash_value: str | None,
|
71
|
+
) -> CompileResult:
|
72
|
+
input_dir = Path(directory)
|
73
|
+
output_dir = input_dir / "fastled_js"
|
74
|
+
start = time.time()
|
75
|
+
web_result = web_compile(
|
76
|
+
directory=input_dir, host=host, build_mode=build_mode, profile=profile
|
77
|
+
)
|
78
|
+
diff = time.time() - start
|
79
|
+
if not web_result.success:
|
80
|
+
print("\nWeb compilation failed:")
|
81
|
+
print(f"Time taken: {diff:.2f} seconds")
|
82
|
+
print(web_result.stdout)
|
83
|
+
# Create error page
|
84
|
+
output_dir.mkdir(exist_ok=True)
|
85
|
+
error_html = _create_error_html(web_result.stdout)
|
86
|
+
(output_dir / "index.html").write_text(error_html, encoding="utf-8")
|
87
|
+
return web_result
|
88
|
+
|
89
|
+
def print_results() -> None:
|
90
|
+
hash_value = (
|
91
|
+
web_result.hash_value
|
92
|
+
if web_result.hash_value is not None
|
93
|
+
else "NO HASH VALUE"
|
94
|
+
)
|
95
|
+
print(
|
96
|
+
f"\nWeb compilation successful\n Time: {diff:.2f}\n output: {output_dir}\n hash: {hash_value}\n zip size: {len(web_result.zip_bytes)} bytes"
|
97
|
+
)
|
98
|
+
|
99
|
+
# now check to see if the hash value is the same as the last hash value
|
100
|
+
if last_hash_value is not None and last_hash_value == web_result.hash_value:
|
101
|
+
print("\nSkipping redeploy: No significant changes found.")
|
102
|
+
print_results()
|
103
|
+
return web_result
|
104
|
+
|
105
|
+
# Extract zip contents to fastled_js directory
|
106
|
+
output_dir.mkdir(exist_ok=True)
|
107
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
108
|
+
temp_path = Path(temp_dir)
|
109
|
+
temp_zip = temp_path / "result.zip"
|
110
|
+
temp_zip.write_bytes(web_result.zip_bytes)
|
111
|
+
|
112
|
+
# Clear existing contents
|
113
|
+
shutil.rmtree(output_dir, ignore_errors=True)
|
114
|
+
output_dir.mkdir(exist_ok=True)
|
115
|
+
|
116
|
+
# Extract zip contents
|
117
|
+
shutil.unpack_archive(temp_zip, output_dir, "zip")
|
118
|
+
|
119
|
+
print(web_result.stdout)
|
120
|
+
print_results()
|
121
|
+
return web_result
|
122
|
+
|
123
|
+
|
124
|
+
def _try_start_server_or_get_url(
|
125
|
+
auto_update: bool, args_web: str | bool, localhost: bool
|
126
|
+
) -> tuple[str, CompileServer | None]:
|
127
|
+
is_local_host = localhost or (
|
128
|
+
isinstance(args_web, str)
|
129
|
+
and ("localhost" in args_web or "127.0.0.1" in args_web)
|
130
|
+
)
|
131
|
+
# test to see if there is already a local host server
|
132
|
+
local_host_needs_server = False
|
133
|
+
if is_local_host:
|
134
|
+
addr = "localhost" if localhost or not isinstance(args_web, str) else args_web
|
135
|
+
urls = [addr]
|
136
|
+
if ":" not in addr:
|
137
|
+
urls.append(f"{addr}:{SERVER_PORT}")
|
138
|
+
|
139
|
+
result: ConnectionResult | None = find_good_connection(urls)
|
140
|
+
if result is not None:
|
141
|
+
print(f"Found local server at {result.host}")
|
142
|
+
return (result.host, None)
|
143
|
+
else:
|
144
|
+
local_host_needs_server = True
|
145
|
+
|
146
|
+
if not local_host_needs_server and args_web:
|
147
|
+
if isinstance(args_web, str):
|
148
|
+
return (args_web, None)
|
149
|
+
if isinstance(args_web, bool):
|
150
|
+
return (DEFAULT_URL, None)
|
151
|
+
return (args_web, None)
|
152
|
+
else:
|
153
|
+
try:
|
154
|
+
print("No local server found, starting one...")
|
155
|
+
compile_server = CompileServer(auto_updates=auto_update)
|
156
|
+
print("Waiting for the local compiler to start...")
|
157
|
+
if not compile_server.ping():
|
158
|
+
print("Failed to start local compiler.")
|
159
|
+
raise CompileServerError("Failed to start local compiler.")
|
160
|
+
return (compile_server.url(), compile_server)
|
161
|
+
except KeyboardInterrupt:
|
162
|
+
raise
|
163
|
+
except RuntimeError:
|
164
|
+
print("Failed to start local compile server, using web compiler instead.")
|
165
|
+
return (DEFAULT_URL, None)
|
166
|
+
|
167
|
+
|
168
|
+
def run_client(
|
169
|
+
directory: Path,
|
170
|
+
host: str | CompileServer | None,
|
171
|
+
open_web_browser: bool = True,
|
172
|
+
keep_running: bool = True, # if false, only one compilation will be done.
|
173
|
+
build_mode: BuildMode = BuildMode.QUICK,
|
174
|
+
profile: bool = False,
|
175
|
+
shutdown: threading.Event | None = None,
|
176
|
+
) -> int:
|
177
|
+
|
178
|
+
compile_server: CompileServer | None = (
|
179
|
+
host if isinstance(host, CompileServer) else None
|
180
|
+
)
|
181
|
+
shutdown = shutdown or threading.Event()
|
182
|
+
|
183
|
+
def get_url() -> str:
|
184
|
+
if compile_server is not None:
|
185
|
+
return compile_server.url()
|
186
|
+
if isinstance(host, str):
|
187
|
+
return host
|
188
|
+
return DEFAULT_URL
|
189
|
+
|
190
|
+
url = get_url()
|
191
|
+
|
192
|
+
try:
|
193
|
+
|
194
|
+
def compile_function(
|
195
|
+
url: str = url,
|
196
|
+
build_mode: BuildMode = build_mode,
|
197
|
+
profile: bool = profile,
|
198
|
+
last_hash_value: str | None = None,
|
199
|
+
) -> CompileResult:
|
200
|
+
TEST_BEFORE_COMPILE(url)
|
201
|
+
return _run_web_compiler(
|
202
|
+
directory,
|
203
|
+
host=url,
|
204
|
+
build_mode=build_mode,
|
205
|
+
profile=profile,
|
206
|
+
last_hash_value=last_hash_value,
|
207
|
+
)
|
208
|
+
|
209
|
+
result: CompileResult = compile_function(last_hash_value=None)
|
210
|
+
last_compiled_result: CompileResult = result
|
211
|
+
|
212
|
+
if not result.success:
|
213
|
+
print("\nCompilation failed.")
|
214
|
+
|
215
|
+
browser_proc: Process | None = None
|
216
|
+
if open_web_browser:
|
217
|
+
browser_proc = open_browser_process(directory / "fastled_js")
|
218
|
+
else:
|
219
|
+
print("\nCompilation successful.")
|
220
|
+
if compile_server:
|
221
|
+
print("Shutting down compile server...")
|
222
|
+
compile_server.stop()
|
223
|
+
return 0
|
224
|
+
|
225
|
+
if not keep_running or shutdown.is_set():
|
226
|
+
if browser_proc:
|
227
|
+
browser_proc.kill()
|
228
|
+
return 0 if result.success else 1
|
229
|
+
except KeyboardInterrupt:
|
230
|
+
print("\nExiting from main")
|
231
|
+
return 1
|
232
|
+
|
233
|
+
excluded_patterns = ["fastled_js"]
|
234
|
+
|
235
|
+
sketch_filewatcher = FileWatcherProcess(
|
236
|
+
directory, excluded_patterns=excluded_patterns
|
237
|
+
)
|
238
|
+
|
239
|
+
source_code_watcher: FileWatcherProcess | None = None
|
240
|
+
if compile_server and compile_server.using_fastled_src_dir_volume():
|
241
|
+
assert compile_server.fastled_src_dir is not None
|
242
|
+
source_code_watcher = FileWatcherProcess(
|
243
|
+
compile_server.fastled_src_dir, excluded_patterns=excluded_patterns
|
244
|
+
)
|
245
|
+
|
246
|
+
def trigger_rebuild_if_sketch_changed(
|
247
|
+
last_compiled_result: CompileResult,
|
248
|
+
) -> tuple[bool, CompileResult]:
|
249
|
+
changed_files = sketch_filewatcher.get_all_changes()
|
250
|
+
if changed_files:
|
251
|
+
print(f"\nChanges detected in {changed_files}")
|
252
|
+
last_hash_value = last_compiled_result.hash_value
|
253
|
+
out = compile_function(last_hash_value=last_hash_value)
|
254
|
+
if not out.success:
|
255
|
+
print("\nRecompilation failed.")
|
256
|
+
else:
|
257
|
+
print("\nRecompilation successful.")
|
258
|
+
return True, out
|
259
|
+
return False, last_compiled_result
|
260
|
+
|
261
|
+
def print_status() -> None:
|
262
|
+
print("Will compile on sketch changes or if you hit the space bar.")
|
263
|
+
|
264
|
+
print_status()
|
265
|
+
print("Press Ctrl+C to stop...")
|
266
|
+
|
267
|
+
try:
|
268
|
+
while True:
|
269
|
+
if shutdown.is_set():
|
270
|
+
print("\nStopping watch mode...")
|
271
|
+
return 0
|
272
|
+
if SpaceBarWatcher.watch_space_bar_pressed(timeout=1.0):
|
273
|
+
print("Compiling...")
|
274
|
+
last_compiled_result = compile_function(last_hash_value=None)
|
275
|
+
if not last_compiled_result.success:
|
276
|
+
print("\nRecompilation failed.")
|
277
|
+
else:
|
278
|
+
print("\nRecompilation successful.")
|
279
|
+
# drain the space bar queue
|
280
|
+
SpaceBarWatcher.watch_space_bar_pressed()
|
281
|
+
print_status()
|
282
|
+
continue
|
283
|
+
changed, last_compiled_result = trigger_rebuild_if_sketch_changed(
|
284
|
+
last_compiled_result
|
285
|
+
)
|
286
|
+
if changed:
|
287
|
+
print_status()
|
288
|
+
continue
|
289
|
+
if compile_server and not compile_server.process_running():
|
290
|
+
print("Server process is not running. Exiting...")
|
291
|
+
return 1
|
292
|
+
if source_code_watcher is not None:
|
293
|
+
changed_files = source_code_watcher.get_all_changes()
|
294
|
+
# de-duplicate changes
|
295
|
+
changed_files = sorted(list(set(changed_files)))
|
296
|
+
if changed_files:
|
297
|
+
print(f"\nChanges detected in FastLED source code: {changed_files}")
|
298
|
+
print("Press space bar to trigger compile.")
|
299
|
+
while True:
|
300
|
+
space_bar_pressed = SpaceBarWatcher.watch_space_bar_pressed(
|
301
|
+
timeout=1.0
|
302
|
+
)
|
303
|
+
file_changes = source_code_watcher.get_all_changes()
|
304
|
+
sketch_files_changed = sketch_filewatcher.get_all_changes()
|
305
|
+
|
306
|
+
if file_changes:
|
307
|
+
print(
|
308
|
+
f"Changes detected in {file_changes}\nHit the space bar to trigger compile."
|
309
|
+
)
|
310
|
+
|
311
|
+
if space_bar_pressed or sketch_files_changed:
|
312
|
+
if space_bar_pressed:
|
313
|
+
print("Space bar pressed, triggering recompile...")
|
314
|
+
elif sketch_files_changed:
|
315
|
+
print(
|
316
|
+
f"Changes detected in {','.join(sketch_files_changed)}, triggering recompile..."
|
317
|
+
)
|
318
|
+
last_compiled_result = compile_function(
|
319
|
+
last_hash_value=None
|
320
|
+
)
|
321
|
+
print("Finished recompile.")
|
322
|
+
# Drain the space bar queue
|
323
|
+
SpaceBarWatcher.watch_space_bar_pressed()
|
324
|
+
print_status()
|
325
|
+
continue
|
326
|
+
|
327
|
+
except KeyboardInterrupt:
|
328
|
+
print("\nStopping watch mode...")
|
329
|
+
return 0
|
330
|
+
except Exception as e:
|
331
|
+
print(f"Error: {e}")
|
332
|
+
return 1
|
333
|
+
finally:
|
334
|
+
sketch_filewatcher.stop()
|
335
|
+
if compile_server:
|
336
|
+
compile_server.stop()
|
337
|
+
if browser_proc:
|
338
|
+
browser_proc.kill()
|
339
|
+
|
340
|
+
|
341
|
+
def run_client_server(args: Args) -> int:
|
342
|
+
profile = bool(args.profile)
|
343
|
+
web: str | bool = args.web if isinstance(args.web, str) else bool(args.web)
|
344
|
+
auto_update = bool(args.auto_update)
|
345
|
+
localhost = bool(args.localhost)
|
346
|
+
directory = args.directory if args.directory else Path(".")
|
347
|
+
just_compile = bool(args.just_compile)
|
348
|
+
interactive = bool(args.interactive)
|
349
|
+
force_compile = bool(args.force_compile)
|
350
|
+
open_web_browser = not just_compile and not interactive
|
351
|
+
build_mode: BuildMode = BuildMode.from_args(args)
|
352
|
+
|
353
|
+
if not force_compile and not looks_like_sketch_directory(directory):
|
354
|
+
# if there is only one directory in the sketch directory, use that
|
355
|
+
found_valid_child = False
|
356
|
+
if len(list(directory.iterdir())) == 1:
|
357
|
+
child_dir = next(directory.iterdir())
|
358
|
+
if looks_like_sketch_directory(child_dir):
|
359
|
+
found_valid_child = True
|
360
|
+
print(
|
361
|
+
f"The selected directory is not a valid FastLED sketch directory, the child directory {child_dir} looks like a sketch directory, using that instead."
|
362
|
+
)
|
363
|
+
directory = child_dir
|
364
|
+
if not found_valid_child:
|
365
|
+
print(
|
366
|
+
f"Error: {directory} is not a valid FastLED sketch directory, if you are sure it is, use --force-compile"
|
367
|
+
)
|
368
|
+
return 1
|
369
|
+
|
370
|
+
# If not explicitly using web compiler, check Docker installation
|
371
|
+
if not web and not DockerManager.is_docker_installed():
|
372
|
+
print(
|
373
|
+
"\nDocker is not installed on this system - switching to web compiler instead."
|
374
|
+
)
|
375
|
+
web = True
|
376
|
+
|
377
|
+
url: str
|
378
|
+
compile_server: CompileServer | None = None
|
379
|
+
try:
|
380
|
+
url, compile_server = _try_start_server_or_get_url(auto_update, web, localhost)
|
381
|
+
except KeyboardInterrupt:
|
382
|
+
print("\nExiting from first try...")
|
383
|
+
if compile_server:
|
384
|
+
compile_server.stop()
|
385
|
+
return 1
|
386
|
+
except Exception as e:
|
387
|
+
print(f"Error: {e}")
|
388
|
+
if compile_server:
|
389
|
+
compile_server.stop()
|
390
|
+
return 1
|
391
|
+
|
392
|
+
try:
|
393
|
+
return run_client(
|
394
|
+
directory=directory,
|
395
|
+
host=compile_server if compile_server else url,
|
396
|
+
open_web_browser=open_web_browser,
|
397
|
+
keep_running=not just_compile,
|
398
|
+
build_mode=build_mode,
|
399
|
+
profile=profile,
|
400
|
+
)
|
401
|
+
except KeyboardInterrupt:
|
402
|
+
return 1
|
403
|
+
finally:
|
404
|
+
if compile_server:
|
405
|
+
compile_server.stop()
|