fastled 1.2.68__tar.gz → 1.2.74__tar.gz
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-1.2.68 → fastled-1.2.74}/Dockerfile +2 -2
- {fastled-1.2.68 → fastled-1.2.74}/PKG-INFO +1 -1
- {fastled-1.2.68 → fastled-1.2.74}/compiler/compile.py +76 -32
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/__init__.py +1 -1
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/app.py +5 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/compile_server_impl.py +26 -16
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/docker_manager.py +142 -17
- fastled-1.2.74/src/fastled/keyz.py +84 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/open_browser.py +3 -13
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/parse_args.py +2 -1
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/string_diff.py +22 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/util.py +9 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled.egg-info/PKG-INFO +1 -1
- fastled-1.2.68/src/fastled/keyz.py +0 -31
- {fastled-1.2.68 → fastled-1.2.74}/.aiderignore +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.dockerignore +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/build_multi_docker_image.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/build_webpage.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/lint.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/publish_release.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/template_build_docker_image.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/test_build_exe.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/test_macos.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/test_ubuntu.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.github/workflows/test_win.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.gitignore +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.pylintrc +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.vscode/launch.json +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.vscode/settings.json +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/.vscode/tasks.json +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/LICENSE +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/MANIFEST.in +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/README.md +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/RELEASE.md +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/TODO.md +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/build_exe.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/build_site.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/clean +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/CMakeLists.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/__init__.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/arduino-pre-process.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/build.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/build_archive.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/build_fast.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/code_sync.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/compile_lock.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/entrypoint.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/extra/100dots.html +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/extra/demo_threejs.html +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/extra/micdemo.html +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/extra/mp3upload.html +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/extra/webgl_postprocessing_unreal_bloom.html +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/final_prewarm.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/init_runtime.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/install-arduino-cli.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/libcompile/CMakeLists.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/paths.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/pre-process.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/prewarm.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/process-ino.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/process_extended.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/pyproject.toml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/run.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/server.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/sketch_hasher.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/compiler/wasm_compiler_flags.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/docker-compose.yml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/install +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/install_linux.sh +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/lint +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/pyproject.toml +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/requirements.testing.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/setup.cfg +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/setup.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/assets/example.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/assets/localhost-key.pem +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/assets/localhost.pem +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/cli.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/cli_test.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/client_server.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/compile_server.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/filewatcher.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/interactive_srcs.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/keyboard.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/live_client.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/paths.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/project_init.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/select_sketch_directory.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/server_fastapi.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/server_fastapi_cli.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/server_flask.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/server_start.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/settings.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/site/build.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/site/examples.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/sketch.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/spinner.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/test/can_run_local_docker_tests.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/test/examples.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/types.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled/web_compile.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled.egg-info/SOURCES.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled.egg-info/dependency_links.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled.egg-info/entry_points.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled.egg-info/requires.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/src/fastled.egg-info/top_level.txt +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/test +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/integration/test_build_examples.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/integration/test_examples.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/html/index.html +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_api.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_bad_ino.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_cli.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_compile_server.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_docker_linux_on_windows.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_embedded_data.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_filechanger.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_http_server.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_ino/bad/bad.ino +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_ino/bad_platformio/bad_platformio.ino +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_ino/bad_platformio/platformio.ini +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_ino/embedded/data/bigdata.dat +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_ino/embedded/wasm.ino +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_ino/wasm/wasm.ino +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_project_init.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_server_and_client_seperatly.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_string_diff.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/tests/unit/test_webcompile.py +0 -0
- {fastled-1.2.68 → fastled-1.2.74}/upload_package.sh +0 -0
@@ -41,8 +41,8 @@
|
|
41
41
|
|
42
42
|
# This will be set to arm64 to support MacOS M1+ devices (and Linux-based arm64 devices)
|
43
43
|
ARG PLATFORM_TAG=""
|
44
|
-
|
45
|
-
ARG EMSDK_VERSION_TAG="3.1.70"
|
44
|
+
ARG EMSDK_VERSION_TAG="4.0.8"
|
45
|
+
# ARG EMSDK_VERSION_TAG="3.1.70"
|
46
46
|
|
47
47
|
# Use only Emscripten base image
|
48
48
|
FROM emscripten/emsdk:${EMSDK_VERSION_TAG}${PLATFORM_TAG}
|
@@ -89,6 +89,33 @@ def copy_files(src_dir: Path, js_src: Path) -> None:
|
|
89
89
|
shutil.copy2(item, js_src / item.name)
|
90
90
|
|
91
91
|
|
92
|
+
def _banner(msg: str) -> str:
|
93
|
+
"""
|
94
|
+
Create a banner for the given message.
|
95
|
+
Example:
|
96
|
+
msg = "Hello, World!"
|
97
|
+
print -> "#################"
|
98
|
+
"# Hello, World! #"
|
99
|
+
"#################"
|
100
|
+
"""
|
101
|
+
lines = msg.split("\n")
|
102
|
+
# Find the width of the widest line
|
103
|
+
max_width = max(len(line) for line in lines)
|
104
|
+
width = max_width + 4 # Add 4 for "# " and " #"
|
105
|
+
|
106
|
+
# Create the top border
|
107
|
+
banner = "\n" + "#" * width + "\n"
|
108
|
+
|
109
|
+
# Add each line with proper padding
|
110
|
+
for line in lines:
|
111
|
+
padding = max_width - len(line)
|
112
|
+
banner += f"# {line}{' ' * padding} #\n"
|
113
|
+
|
114
|
+
# Add the bottom border
|
115
|
+
banner += "#" * width + "\n"
|
116
|
+
return banner
|
117
|
+
|
118
|
+
|
92
119
|
def compile(
|
93
120
|
compiler_root: Path, build_mode: BuildMode, auto_clean: bool, no_platformio: bool
|
94
121
|
) -> int:
|
@@ -96,7 +123,7 @@ def compile(
|
|
96
123
|
max_attempts = 1
|
97
124
|
env = os.environ.copy()
|
98
125
|
env["BUILD_MODE"] = build_mode.name
|
99
|
-
print(f"
|
126
|
+
print(_banner(f"WASM is building in mode: {build_mode.name}"))
|
100
127
|
cmd_list: list[str] = []
|
101
128
|
if no_platformio:
|
102
129
|
# execute build_archive.syh
|
@@ -113,6 +140,7 @@ def compile(
|
|
113
140
|
cmd_list.append("-v")
|
114
141
|
|
115
142
|
def _open_process(cmd_list: list[str] = cmd_list) -> subprocess.Popen:
|
143
|
+
print(_banner("Running command:\n " + subprocess.list2cmdline(cmd_list)))
|
116
144
|
out = subprocess.Popen(
|
117
145
|
cmd_list,
|
118
146
|
cwd=compiler_root,
|
@@ -138,7 +166,7 @@ def compile(
|
|
138
166
|
relative_output = _make_timestamps_relative("\n".join(output_lines))
|
139
167
|
print(relative_output)
|
140
168
|
if process.returncode == 0:
|
141
|
-
print(f"Compilation successful on attempt {attempt}")
|
169
|
+
print(_banner(f"Compilation successful on attempt {attempt}"))
|
142
170
|
return 0
|
143
171
|
else:
|
144
172
|
raise subprocess.CalledProcessError(process.returncode, ["pio", "run"])
|
@@ -199,7 +227,7 @@ def transform_to_cpp(src_dir: Path) -> None:
|
|
199
227
|
def insert_headers(
|
200
228
|
src_dir: Path, exclusion_folders: List[Path], file_extensions: List[str]
|
201
229
|
) -> None:
|
202
|
-
print("Inserting headers in source files...")
|
230
|
+
print(_banner("Inserting headers in source files..."))
|
203
231
|
for file in src_dir.rglob("*"):
|
204
232
|
if (
|
205
233
|
file.suffix in file_extensions
|
@@ -213,7 +241,7 @@ def process_ino_files(src_dir: Path) -> None:
|
|
213
241
|
transform_to_cpp(src_dir)
|
214
242
|
exclusion_folders: List[Path] = []
|
215
243
|
insert_headers(src_dir, exclusion_folders, _FILE_EXTENSIONS)
|
216
|
-
print("Transform to cpp and insert header operations completed.")
|
244
|
+
print(_banner("Transform to cpp and insert header operations completed."))
|
217
245
|
|
218
246
|
|
219
247
|
def _make_timestamps_relative(stdout: str) -> str:
|
@@ -381,7 +409,7 @@ def process_compile(
|
|
381
409
|
if rtn != 0:
|
382
410
|
print("Compilation failed.")
|
383
411
|
raise RuntimeError("Compilation failed.")
|
384
|
-
print("Compilation successful.")
|
412
|
+
print(_banner("Compilation successful."))
|
385
413
|
|
386
414
|
|
387
415
|
def cleanup(args: Args, js_src: Path) -> None:
|
@@ -422,9 +450,15 @@ def run(args: Args) -> int:
|
|
422
450
|
print(f"Using mapped directory: {args.mapped_dir}")
|
423
451
|
|
424
452
|
if args.profile:
|
425
|
-
print("Enabling profiling for compilation.")
|
453
|
+
print(_banner("Enabling profiling for compilation."))
|
426
454
|
# Profile linking
|
427
455
|
os.environ["EMPROFILE"] = "2"
|
456
|
+
else:
|
457
|
+
print(
|
458
|
+
_banner(
|
459
|
+
"Build process profiling is disabled\nuse --profile to get metrics on how long the build process took."
|
460
|
+
)
|
461
|
+
)
|
428
462
|
|
429
463
|
try:
|
430
464
|
|
@@ -477,6 +511,8 @@ def run(args: Args) -> int:
|
|
477
511
|
return 1
|
478
512
|
|
479
513
|
def _get_build_dir_platformio() -> Path:
|
514
|
+
# First assert there is only one build artifact directory.
|
515
|
+
# The name is dynamic: it's your sketch folder name.
|
480
516
|
build_dirs = [d for d in PIO_BUILD_DIR.iterdir() if d.is_dir()]
|
481
517
|
if len(build_dirs) != 1:
|
482
518
|
raise RuntimeError(
|
@@ -493,16 +529,17 @@ def run(args: Args) -> int:
|
|
493
529
|
else:
|
494
530
|
build_dir = _get_build_dir_platformio()
|
495
531
|
|
496
|
-
print("Copying output files...")
|
532
|
+
print(_banner("Copying output files..."))
|
497
533
|
out_dir: Path = src_dir / _FASTLED_OUTPUT_DIR_NAME
|
498
534
|
out_dir.mkdir(parents=True, exist_ok=True)
|
499
535
|
|
500
|
-
|
501
|
-
|
502
|
-
_dst = out_dir /
|
503
|
-
print(f"Copying {
|
504
|
-
shutil.copy2(
|
536
|
+
# Copy all fastled.* build artifacts
|
537
|
+
for file_path in build_dir.glob("fastled.*"):
|
538
|
+
_dst = out_dir / file_path.name
|
539
|
+
print(f"Copying {file_path} to {_dst}")
|
540
|
+
shutil.copy2(file_path, _dst)
|
505
541
|
|
542
|
+
# Copy static files.
|
506
543
|
print(f"Copying {_INDEX_HTML_SRC} to output directory")
|
507
544
|
shutil.copy2(_INDEX_HTML_SRC, out_dir / "index.html")
|
508
545
|
print(f"Copying {_INDEX_CSS_SRC} to output directory")
|
@@ -510,23 +547,30 @@ def run(args: Args) -> int:
|
|
510
547
|
|
511
548
|
# copy all js files in _FASTLED_COMPILER_DIR to output directory
|
512
549
|
Path(out_dir / "modules").mkdir(parents=True, exist_ok=True)
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
if
|
528
|
-
|
529
|
-
|
550
|
+
|
551
|
+
# Recursively copy all non-hidden files and directories
|
552
|
+
print(f"Copying files from {_FASTLED_MODULES_DIR} to {out_dir / 'modules'}")
|
553
|
+
shutil.copytree(
|
554
|
+
src=_FASTLED_MODULES_DIR,
|
555
|
+
dst=out_dir / "modules",
|
556
|
+
dirs_exist_ok=True,
|
557
|
+
ignore=shutil.ignore_patterns(".*"),
|
558
|
+
) # Ignore hidden files
|
559
|
+
|
560
|
+
# Now long needed since now we do glob copy.
|
561
|
+
# fastled_js_mem = build_dir / "fastled.js.mem"
|
562
|
+
# fastled_wasm_map = build_dir / "fastled.wasm.map"
|
563
|
+
# fastled_js_symbols = build_dir / "fastled.js.symbols"
|
564
|
+
# if fastled_js_mem.exists():
|
565
|
+
# print(f"Copying {fastled_js_mem} to output directory")
|
566
|
+
# shutil.copy2(fastled_js_mem, out_dir / fastled_js_mem.name)
|
567
|
+
# if fastled_wasm_map.exists():
|
568
|
+
# print(f"Copying {fastled_wasm_map} to output directory")
|
569
|
+
# shutil.copy2(fastled_wasm_map, out_dir / fastled_wasm_map.name)
|
570
|
+
# if fastled_js_symbols.exists():
|
571
|
+
# print(f"Copying {fastled_js_symbols} to output directory")
|
572
|
+
# shutil.copy2(fastled_js_symbols, out_dir / fastled_js_symbols.name)
|
573
|
+
|
530
574
|
print("Copying index.js to output directory")
|
531
575
|
shutil.copy2(_INDEX_JS_SRC, out_dir / "index.js")
|
532
576
|
optional_input_data_dir = src_dir / "data"
|
@@ -546,7 +590,7 @@ def run(args: Args) -> int:
|
|
546
590
|
if _file.is_file(): # Only copy files, not directories
|
547
591
|
filename: str = _file.name
|
548
592
|
if filename.endswith(".embedded.json"):
|
549
|
-
print("Embedding data file")
|
593
|
+
print(_banner("Embedding data file"))
|
550
594
|
filename_no_embedded = filename.replace(
|
551
595
|
".embedded.json", ""
|
552
596
|
)
|
@@ -577,13 +621,13 @@ def run(args: Args) -> int:
|
|
577
621
|
)
|
578
622
|
|
579
623
|
# Write manifest file even if empty
|
580
|
-
print("Writing manifest files.json")
|
624
|
+
print(_banner("Writing manifest files.json"))
|
581
625
|
manifest_json_str = json.dumps(manifest, indent=2, sort_keys=True)
|
582
626
|
with open(out_dir / "files.json", "w") as f:
|
583
627
|
f.write(manifest_json_str)
|
584
628
|
cleanup(args, SKETCH_SRC)
|
585
629
|
|
586
|
-
print("Compilation process completed successfully")
|
630
|
+
print(_banner("Compilation process completed successfully"))
|
587
631
|
return 0
|
588
632
|
|
589
633
|
except Exception as e:
|
@@ -13,7 +13,7 @@ from .types import BuildMode, CompileResult, CompileServerError
|
|
13
13
|
# IMPORTANT! There's a bug in github which will REJECT any version update
|
14
14
|
# that has any other change in the repo. Please bump the version as the
|
15
15
|
# ONLY change in a commit, or else the pypi update and the release will fail.
|
16
|
-
__version__ = "1.2.
|
16
|
+
__version__ = "1.2.74"
|
17
17
|
|
18
18
|
DOCKER_FILE = (
|
19
19
|
"https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/Dockerfile"
|
@@ -43,6 +43,8 @@ def run_server(args: Args) -> int:
|
|
43
43
|
|
44
44
|
|
45
45
|
def main() -> int:
|
46
|
+
from fastled import __version__
|
47
|
+
|
46
48
|
args = parse_args()
|
47
49
|
interactive: bool = args.interactive
|
48
50
|
has_server = args.server
|
@@ -52,6 +54,9 @@ def main() -> int:
|
|
52
54
|
# directory: Path | None = Path(args.directory).absolute() if args.directory else None
|
53
55
|
directory: Path | None = Path(args.directory) if args.directory else None
|
54
56
|
|
57
|
+
# now it is safe to print out the version
|
58
|
+
print(f"FastLED version: {__version__}")
|
59
|
+
|
55
60
|
if update:
|
56
61
|
# Force auto_update to ensure update check happens
|
57
62
|
compile_server = CompileServer(interactive=False, auto_updates=True)
|
@@ -13,6 +13,7 @@ from fastled.docker_manager import (
|
|
13
13
|
Container,
|
14
14
|
DockerManager,
|
15
15
|
RunningContainer,
|
16
|
+
Volume,
|
16
17
|
)
|
17
18
|
from fastled.interactive_srcs import INTERACTIVE_SOURCES
|
18
19
|
from fastled.settings import DEFAULT_CONTAINER_NAME, IMAGE_NAME, SERVER_PORT
|
@@ -209,27 +210,32 @@ class CompileServerImpl:
|
|
209
210
|
else:
|
210
211
|
server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
|
211
212
|
ports = {80: port}
|
212
|
-
volumes =
|
213
|
+
volumes = []
|
213
214
|
if self.fastled_src_dir:
|
214
215
|
print(
|
215
216
|
f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
|
216
217
|
)
|
217
|
-
volumes
|
218
|
-
|
219
|
-
|
218
|
+
volumes.append(
|
219
|
+
Volume(
|
220
|
+
host_path=str(self.fastled_src_dir),
|
221
|
+
container_path="/host/fastled/src",
|
222
|
+
mode="ro",
|
223
|
+
)
|
224
|
+
)
|
220
225
|
if self.interactive:
|
221
226
|
# add the mapped directory to the container
|
222
227
|
print(f"Mounting {self.mapped_dir} into container /mapped")
|
223
|
-
# volumes = {str(self.mapped_dir): {"bind": "/mapped", "mode": "rw"}}
|
224
|
-
# add it
|
225
228
|
assert self.mapped_dir is not None
|
226
229
|
dir_name = self.mapped_dir.name
|
227
230
|
if not volumes:
|
228
|
-
volumes =
|
229
|
-
volumes
|
230
|
-
|
231
|
-
|
232
|
-
|
231
|
+
volumes = []
|
232
|
+
volumes.append(
|
233
|
+
Volume(
|
234
|
+
host_path=str(self.mapped_dir),
|
235
|
+
container_path=f"/mapped/{dir_name}",
|
236
|
+
mode="rw",
|
237
|
+
)
|
238
|
+
)
|
233
239
|
if self.fastled_src_dir is not None:
|
234
240
|
# to allow for interactive compilation
|
235
241
|
interactive_sources = list(INTERACTIVE_SOURCES)
|
@@ -237,11 +243,13 @@ class CompileServerImpl:
|
|
237
243
|
src_path = Path(src).absolute()
|
238
244
|
if src_path.exists():
|
239
245
|
print(f"Mounting {src} into container")
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
246
|
+
volumes.append(
|
247
|
+
Volume(
|
248
|
+
host_path=str(src_path),
|
249
|
+
container_path=f"/js/fastled/{src}",
|
250
|
+
mode="rw",
|
251
|
+
)
|
252
|
+
)
|
245
253
|
else:
|
246
254
|
print(f"Could not find {src}")
|
247
255
|
|
@@ -277,6 +285,8 @@ class CompileServerImpl:
|
|
277
285
|
return self.docker.is_container_running(self.container_name)
|
278
286
|
|
279
287
|
def stop(self) -> None:
|
288
|
+
if self.docker.is_suspended:
|
289
|
+
return
|
280
290
|
if self.running_container:
|
281
291
|
self.running_container.detach()
|
282
292
|
self.running_container = None
|
@@ -3,6 +3,7 @@ New abstraction for Docker management with improved Ctrl+C handling.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import _thread
|
6
|
+
import json
|
6
7
|
import os
|
7
8
|
import platform
|
8
9
|
import subprocess
|
@@ -11,6 +12,7 @@ import threading
|
|
11
12
|
import time
|
12
13
|
import traceback
|
13
14
|
import warnings
|
15
|
+
from dataclasses import dataclass
|
14
16
|
from datetime import datetime, timezone
|
15
17
|
from pathlib import Path
|
16
18
|
|
@@ -63,8 +65,69 @@ def get_lock(image_name: str) -> FileLock:
|
|
63
65
|
return out
|
64
66
|
|
65
67
|
|
68
|
+
@dataclass
|
69
|
+
class Volume:
|
70
|
+
"""
|
71
|
+
Represents a Docker volume mapping between host and container.
|
72
|
+
|
73
|
+
Attributes:
|
74
|
+
host_path: Path on the host system (e.g., "C:\\Users\\username\\project")
|
75
|
+
container_path: Path inside the container (e.g., "/app/data")
|
76
|
+
mode: Access mode, "rw" for read-write or "ro" for read-only
|
77
|
+
"""
|
78
|
+
|
79
|
+
host_path: str
|
80
|
+
container_path: str
|
81
|
+
mode: str = "rw"
|
82
|
+
|
83
|
+
def to_dict(self) -> dict[str, dict[str, str]]:
|
84
|
+
"""Convert the Volume object to the format expected by Docker API."""
|
85
|
+
return {self.host_path: {"bind": self.container_path, "mode": self.mode}}
|
86
|
+
|
87
|
+
@classmethod
|
88
|
+
def from_dict(cls, volume_dict: dict[str, dict[str, str]]) -> list["Volume"]:
|
89
|
+
"""Create Volume objects from a Docker volume dictionary."""
|
90
|
+
volumes = []
|
91
|
+
for host_path, config in volume_dict.items():
|
92
|
+
volumes.append(
|
93
|
+
cls(
|
94
|
+
host_path=host_path,
|
95
|
+
container_path=config["bind"],
|
96
|
+
mode=config.get("mode", "rw"),
|
97
|
+
)
|
98
|
+
)
|
99
|
+
return volumes
|
100
|
+
|
101
|
+
|
102
|
+
class PrintFilter:
|
103
|
+
"""Provides filtering for text output so that source files match up with local names."""
|
104
|
+
|
105
|
+
def __init__(self) -> None:
|
106
|
+
pass
|
107
|
+
|
108
|
+
def _remove_ino_cpp(self, text: str) -> str:
|
109
|
+
return text.replace(".ino.cpp", ".ino")
|
110
|
+
|
111
|
+
def _filter_all(self, text: str) -> str:
|
112
|
+
text = self._remove_ino_cpp(text)
|
113
|
+
return text
|
114
|
+
|
115
|
+
def print(self, text: str | bytes) -> None:
|
116
|
+
"""Prints the text to the console."""
|
117
|
+
if isinstance(text, bytes):
|
118
|
+
text = text.decode("utf-8")
|
119
|
+
text = self._filter_all(text)
|
120
|
+
print(text, end="")
|
121
|
+
|
122
|
+
|
66
123
|
class RunningContainer:
|
67
|
-
def __init__(
|
124
|
+
def __init__(
|
125
|
+
self,
|
126
|
+
container: Container,
|
127
|
+
first_run: bool = False,
|
128
|
+
filter: PrintFilter | None = None,
|
129
|
+
) -> None:
|
130
|
+
self.filter = filter or PrintFilter()
|
68
131
|
self.container = container
|
69
132
|
self.first_run = first_run
|
70
133
|
self.running = True
|
@@ -81,7 +144,8 @@ class RunningContainer:
|
|
81
144
|
for log in self.container.logs(
|
82
145
|
follow=False, since=from_date, until=to_date, stream=True
|
83
146
|
):
|
84
|
-
print(log.decode("utf-8"), end="")
|
147
|
+
# print(log.decode("utf-8"), end="")
|
148
|
+
self.filter.print(log)
|
85
149
|
time.sleep(0.1)
|
86
150
|
from_date = to_date
|
87
151
|
to_date = _utc_now_no_tz()
|
@@ -104,10 +168,46 @@ class RunningContainer:
|
|
104
168
|
self.detach()
|
105
169
|
|
106
170
|
|
171
|
+
def _hack_to_fix_mac(volumes: list[Volume] | None) -> list[Volume] | None:
|
172
|
+
"""Fixes the volume mounts on MacOS by removing the mode."""
|
173
|
+
if volumes is None:
|
174
|
+
return None
|
175
|
+
if sys.platform != "darwin":
|
176
|
+
# Only macos needs hacking.
|
177
|
+
return volumes
|
178
|
+
|
179
|
+
volumes = volumes.copy()
|
180
|
+
# Work around a Docker bug on MacOS where the expected network socket to the
|
181
|
+
# the host is not mounted correctly. This was actually fixed in recent versions
|
182
|
+
# of docker client but there is a large chunk of Docker clients out there with
|
183
|
+
# this bug in it.
|
184
|
+
#
|
185
|
+
# This hack is done by mounting the socket directly to the container.
|
186
|
+
# This socket talks to the docker daemon on the host.
|
187
|
+
#
|
188
|
+
# Found here.
|
189
|
+
# https://github.com/docker/docker-py/issues/3069#issuecomment-1316778735
|
190
|
+
# if it exists already then return the input
|
191
|
+
for volume in volumes:
|
192
|
+
if volume.host_path == "/var/run/docker.sock":
|
193
|
+
return volumes
|
194
|
+
# ok it doesn't exist, so add it
|
195
|
+
volumes.append(
|
196
|
+
Volume(
|
197
|
+
host_path="/var/run/docker.sock",
|
198
|
+
container_path="/var/run/docker.sock",
|
199
|
+
mode="rw",
|
200
|
+
)
|
201
|
+
)
|
202
|
+
return volumes
|
203
|
+
|
204
|
+
|
107
205
|
class DockerManager:
|
108
206
|
def __init__(self) -> None:
|
109
207
|
from docker.errors import DockerException
|
110
208
|
|
209
|
+
self.is_suspended: bool = False
|
210
|
+
|
111
211
|
try:
|
112
212
|
self._client: DockerClient | None = None
|
113
213
|
self.first_run = False
|
@@ -314,8 +414,6 @@ class DockerManager:
|
|
314
414
|
|
315
415
|
# Quick check for latest version
|
316
416
|
with Spinner(f"Pulling newer version of {image_name}:{tag}..."):
|
317
|
-
# This needs to be swapped out using the the command line interface AI!
|
318
|
-
# _ = self.client.images.pull(image_name, tag=tag)
|
319
417
|
cmd_list = ["docker", "pull", f"{image_name}:{tag}"]
|
320
418
|
cmd_str = subprocess.list2cmdline(cmd_list)
|
321
419
|
print(f"Running command: {cmd_str}")
|
@@ -355,8 +453,8 @@ class DockerManager:
|
|
355
453
|
self,
|
356
454
|
container: Container,
|
357
455
|
command: str | None,
|
358
|
-
|
359
|
-
ports: dict | None,
|
456
|
+
volumes_dict: dict[str, dict[str, str]] | None,
|
457
|
+
ports: dict[int, int] | None,
|
360
458
|
) -> bool:
|
361
459
|
"""Compare if existing container has matching configuration"""
|
362
460
|
try:
|
@@ -386,7 +484,7 @@ class DockerManager:
|
|
386
484
|
return False
|
387
485
|
|
388
486
|
# Check volumes if specified
|
389
|
-
if
|
487
|
+
if volumes_dict:
|
390
488
|
container_mounts = (
|
391
489
|
{
|
392
490
|
m["Source"]: {"bind": m["Destination"], "mode": m["Mode"]}
|
@@ -396,7 +494,7 @@ class DockerManager:
|
|
396
494
|
else {}
|
397
495
|
)
|
398
496
|
|
399
|
-
for host_dir, mount in
|
497
|
+
for host_dir, mount in volumes_dict.items():
|
400
498
|
if host_dir not in container_mounts:
|
401
499
|
print(f"Volume {host_dir} not found in container mounts.")
|
402
500
|
return False
|
@@ -446,7 +544,7 @@ class DockerManager:
|
|
446
544
|
tag: str,
|
447
545
|
container_name: str,
|
448
546
|
command: str | None = None,
|
449
|
-
volumes:
|
547
|
+
volumes: list[Volume] | None = None,
|
450
548
|
ports: dict[int, int] | None = None,
|
451
549
|
remove_previous: bool = False,
|
452
550
|
) -> Container:
|
@@ -455,11 +553,23 @@ class DockerManager:
|
|
455
553
|
If it exists with different config, remove and recreate it.
|
456
554
|
|
457
555
|
Args:
|
458
|
-
volumes:
|
459
|
-
Example: {'/host/path': {'bind': '/container/path', 'mode': 'rw'}}
|
556
|
+
volumes: List of Volume objects for container volume mappings
|
460
557
|
ports: Dict mapping host ports to container ports
|
461
558
|
Example: {8080: 80} maps host port 8080 to container port 80
|
462
559
|
"""
|
560
|
+
volumes = _hack_to_fix_mac(volumes)
|
561
|
+
# Convert volumes to the format expected by Docker API
|
562
|
+
volumes_dict = None
|
563
|
+
if volumes is not None:
|
564
|
+
volumes_dict = {}
|
565
|
+
for volume in volumes:
|
566
|
+
volumes_dict.update(volume.to_dict())
|
567
|
+
|
568
|
+
# Serialize the volumes to a json string
|
569
|
+
if volumes_dict:
|
570
|
+
volumes_str = json.dumps(volumes_dict)
|
571
|
+
print(f"Volumes: {volumes_str}")
|
572
|
+
print("Done")
|
463
573
|
image_name = f"{image_name}:{tag}"
|
464
574
|
try:
|
465
575
|
container: Container = self.client.containers.get(container_name)
|
@@ -469,7 +579,9 @@ class DockerManager:
|
|
469
579
|
container.remove(force=True)
|
470
580
|
raise NotFound("Container removed due to remove_previous")
|
471
581
|
# Check if configuration matches
|
472
|
-
elif not self._container_configs_match(
|
582
|
+
elif not self._container_configs_match(
|
583
|
+
container, command, volumes_dict, ports
|
584
|
+
):
|
473
585
|
print(
|
474
586
|
f"Container {container_name} exists but with different configuration. Removing and recreating..."
|
475
587
|
)
|
@@ -517,7 +629,7 @@ class DockerManager:
|
|
517
629
|
name=container_name,
|
518
630
|
detach=True,
|
519
631
|
tty=True,
|
520
|
-
volumes=
|
632
|
+
volumes=volumes_dict,
|
521
633
|
ports=ports, # type: ignore
|
522
634
|
remove=True,
|
523
635
|
)
|
@@ -529,9 +641,16 @@ class DockerManager:
|
|
529
641
|
tag: str,
|
530
642
|
container_name: str,
|
531
643
|
command: str | None = None,
|
532
|
-
volumes:
|
644
|
+
volumes: list[Volume] | None = None,
|
533
645
|
ports: dict[int, int] | None = None,
|
534
646
|
) -> None:
|
647
|
+
# Convert volumes to the format expected by Docker API
|
648
|
+
volumes = _hack_to_fix_mac(volumes)
|
649
|
+
volumes_dict = None
|
650
|
+
if volumes is not None:
|
651
|
+
volumes_dict = {}
|
652
|
+
for volume in volumes:
|
653
|
+
volumes_dict.update(volume.to_dict())
|
535
654
|
# Remove existing container
|
536
655
|
try:
|
537
656
|
container: Container = self.client.containers.get(container_name)
|
@@ -548,9 +667,13 @@ class DockerManager:
|
|
548
667
|
"--name",
|
549
668
|
container_name,
|
550
669
|
]
|
551
|
-
if
|
552
|
-
for host_dir, mount in
|
553
|
-
|
670
|
+
if volumes_dict:
|
671
|
+
for host_dir, mount in volumes_dict.items():
|
672
|
+
docker_volume_arg = [
|
673
|
+
"-v",
|
674
|
+
f"{host_dir}:{mount['bind']}:{mount['mode']}",
|
675
|
+
]
|
676
|
+
docker_command.extend(docker_volume_arg)
|
554
677
|
if ports:
|
555
678
|
for host_port, container_port in ports.items():
|
556
679
|
docker_command.extend(["-p", f"{host_port}:{container_port}"])
|
@@ -591,6 +714,8 @@ class DockerManager:
|
|
591
714
|
"""
|
592
715
|
Suspend (pause) the container.
|
593
716
|
"""
|
717
|
+
if self.is_suspended:
|
718
|
+
return
|
594
719
|
if isinstance(container, str):
|
595
720
|
container_name = container
|
596
721
|
# container = self.get_container(container)
|