fastled 1.1.2__tar.gz → 1.1.6__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.
Files changed (91) hide show
  1. {fastled-1.1.2 → fastled-1.1.6}/PKG-INFO +5 -1
  2. {fastled-1.1.2 → fastled-1.1.6}/README.md +4 -0
  3. {fastled-1.1.2 → fastled-1.1.6}/pyproject.toml +1 -1
  4. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/app.py +3 -9
  5. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/compile_server.py +6 -2
  6. fastled-1.1.6/src/fastled/sketch.py +18 -0
  7. fastled-1.1.6/src/fastled/web_compile.py +227 -0
  8. {fastled-1.1.2 → fastled-1.1.6}/src/fastled.egg-info/PKG-INFO +5 -1
  9. {fastled-1.1.2 → fastled-1.1.6}/src/fastled.egg-info/SOURCES.txt +1 -0
  10. fastled-1.1.2/src/fastled/web_compile.py +0 -220
  11. {fastled-1.1.2 → fastled-1.1.6}/.aiderignore +0 -0
  12. {fastled-1.1.2 → fastled-1.1.6}/.github/workflows/build_multi_docker_image.yml +0 -0
  13. {fastled-1.1.2 → fastled-1.1.6}/.github/workflows/lint.yml +0 -0
  14. {fastled-1.1.2 → fastled-1.1.6}/.github/workflows/test_macos.yml +0 -0
  15. {fastled-1.1.2 → fastled-1.1.6}/.github/workflows/test_ubuntu.yml +0 -0
  16. {fastled-1.1.2 → fastled-1.1.6}/.github/workflows/test_win.yml +0 -0
  17. {fastled-1.1.2 → fastled-1.1.6}/.gitignore +0 -0
  18. {fastled-1.1.2 → fastled-1.1.6}/.pylintrc +0 -0
  19. {fastled-1.1.2 → fastled-1.1.6}/.vscode/launch.json +0 -0
  20. {fastled-1.1.2 → fastled-1.1.6}/.vscode/settings.json +0 -0
  21. {fastled-1.1.2 → fastled-1.1.6}/.vscode/tasks.json +0 -0
  22. {fastled-1.1.2 → fastled-1.1.6}/LICENSE +0 -0
  23. {fastled-1.1.2 → fastled-1.1.6}/MANIFEST.in +0 -0
  24. {fastled-1.1.2 → fastled-1.1.6}/clean +0 -0
  25. {fastled-1.1.2 → fastled-1.1.6}/docs/fastled.js +0 -0
  26. {fastled-1.1.2 → fastled-1.1.6}/docs/fastled.wasm +0 -0
  27. {fastled-1.1.2 → fastled-1.1.6}/docs/index.css +0 -0
  28. {fastled-1.1.2 → fastled-1.1.6}/docs/index.html +0 -0
  29. {fastled-1.1.2 → fastled-1.1.6}/docs/index.js +0 -0
  30. {fastled-1.1.2 → fastled-1.1.6}/examples/Blink/Blink.ino +0 -0
  31. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/Chromancer.ino +0 -0
  32. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/detail.h +0 -0
  33. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/gary_woos_wled_port/gary_woos_wled_ledmap.h +0 -0
  34. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/gary_woos_wled_port/presets.json +0 -0
  35. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/gary_woos_wled_port/presets.min.json +0 -0
  36. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/gen.py +0 -0
  37. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/mapping.h +0 -0
  38. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/net.h +0 -0
  39. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/output.json +0 -0
  40. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/ripple.h +0 -0
  41. {fastled-1.1.2 → fastled-1.1.6}/examples/Chromancer/screenmap.json.h +0 -0
  42. {fastled-1.1.2 → fastled-1.1.6}/examples/ColorPalette/ColorPalette.ino +0 -0
  43. {fastled-1.1.2 → fastled-1.1.6}/examples/ColorTemperature/ColorTemperature.ino +0 -0
  44. {fastled-1.1.2 → fastled-1.1.6}/examples/Cylon/Cylon.ino +0 -0
  45. {fastled-1.1.2 → fastled-1.1.6}/examples/DemoReel100/DemoReel100.ino +0 -0
  46. {fastled-1.1.2 → fastled-1.1.6}/examples/Esp32Rmt51/Esp32Rmt51.ino +0 -0
  47. {fastled-1.1.2 → fastled-1.1.6}/examples/EspI2SDemo/EspI2SDemo.ino +0 -0
  48. {fastled-1.1.2 → fastled-1.1.6}/examples/Fire2012/Fire2012.ino +0 -0
  49. {fastled-1.1.2 → fastled-1.1.6}/examples/Fire2012WithPalette/Fire2012WithPalette.ino +0 -0
  50. {fastled-1.1.2 → fastled-1.1.6}/examples/FirstLight/FirstLight.ino +0 -0
  51. {fastled-1.1.2 → fastled-1.1.6}/examples/FxEngine/FxEngine.ino +0 -0
  52. {fastled-1.1.2 → fastled-1.1.6}/examples/Noise/Noise.ino +0 -0
  53. {fastled-1.1.2 → fastled-1.1.6}/examples/NoisePlayground/NoisePlayground.ino +0 -0
  54. {fastled-1.1.2 → fastled-1.1.6}/examples/NoisePlusPalette/NoisePlusPalette.ino +0 -0
  55. {fastled-1.1.2 → fastled-1.1.6}/examples/OctoWS2811/OctoWS2811.ino +0 -0
  56. {fastled-1.1.2 → fastled-1.1.6}/examples/Pacifica/Pacifica.ino +0 -0
  57. {fastled-1.1.2 → fastled-1.1.6}/examples/Pride2015/Pride2015.ino +0 -0
  58. {fastled-1.1.2 → fastled-1.1.6}/examples/SdCard/SdCard.ino +0 -0
  59. {fastled-1.1.2 → fastled-1.1.6}/examples/TwinkleFox/TwinkleFox.ino +0 -0
  60. {fastled-1.1.2 → fastled-1.1.6}/examples/Video/Gfx2Video/Gfx2Video.ino +0 -0
  61. {fastled-1.1.2 → fastled-1.1.6}/examples/WasmScreenCoords/WasmScreenCoords.ino +0 -0
  62. {fastled-1.1.2 → fastled-1.1.6}/examples/Water/Water.ino +0 -0
  63. {fastled-1.1.2 → fastled-1.1.6}/examples/XYMatrix/XYMatrix.ino +0 -0
  64. {fastled-1.1.2 → fastled-1.1.6}/examples/wasm/wasm.ino +0 -0
  65. {fastled-1.1.2 → fastled-1.1.6}/install +0 -0
  66. {fastled-1.1.2 → fastled-1.1.6}/lint +0 -0
  67. {fastled-1.1.2 → fastled-1.1.6}/requirements.testing.txt +0 -0
  68. {fastled-1.1.2 → fastled-1.1.6}/setup.cfg +0 -0
  69. {fastled-1.1.2 → fastled-1.1.6}/setup.py +0 -0
  70. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/__init__.py +0 -0
  71. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/assets/example.txt +0 -0
  72. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/build_mode.py +0 -0
  73. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/check_cpp_syntax.py +0 -0
  74. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/cli.py +0 -0
  75. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/docker_manager.py +0 -0
  76. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/filewatcher.py +0 -0
  77. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/open_browser.py +0 -0
  78. {fastled-1.1.2 → fastled-1.1.6}/src/fastled/paths.py +0 -0
  79. {fastled-1.1.2 → fastled-1.1.6}/src/fastled.egg-info/dependency_links.txt +0 -0
  80. {fastled-1.1.2 → fastled-1.1.6}/src/fastled.egg-info/entry_points.txt +0 -0
  81. {fastled-1.1.2 → fastled-1.1.6}/src/fastled.egg-info/requires.txt +0 -0
  82. {fastled-1.1.2 → fastled-1.1.6}/src/fastled.egg-info/top_level.txt +0 -0
  83. {fastled-1.1.2 → fastled-1.1.6}/test +0 -0
  84. {fastled-1.1.2 → fastled-1.1.6}/tests/test_bad_ino.py +0 -0
  85. {fastled-1.1.2 → fastled-1.1.6}/tests/test_cli.py +0 -0
  86. {fastled-1.1.2 → fastled-1.1.6}/tests/test_compile_server.py +0 -0
  87. {fastled-1.1.2 → fastled-1.1.6}/tests/test_filechanger.py +0 -0
  88. {fastled-1.1.2 → fastled-1.1.6}/tests/test_ino/bad/bad.ino +0 -0
  89. {fastled-1.1.2 → fastled-1.1.6}/tests/test_ino/wasm/wasm.ino +0 -0
  90. {fastled-1.1.2 → fastled-1.1.6}/tests/test_webcompile.py +0 -0
  91. {fastled-1.1.2 → fastled-1.1.6}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastled
3
- Version: 1.1.2
3
+ Version: 1.1.6
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -91,6 +91,10 @@ provide shims for most of the common api points.
91
91
 
92
92
  # Revisions
93
93
 
94
+ * 1.1.6 - Use the fast src volume map allow quick updates to fastled when developing on the source code.
95
+ * 1.1.5 - Filter out hidden files and directories from being included in the sketch archive sent to the compiler.
96
+ * 1.1.4 - Fix regression introduced by testing out ipv4/ipv6 connections from a thread pool.
97
+ * 1.1.3 - Live editing of *.h and *.cpp files is now possible. Sketch cache will be disabled in this mode.
94
98
  * 1.1.2 - `--server` will now volume map fastled src directory if it detects this. This was also implemented on the docker side.
95
99
  * 1.1.1 - `--interactive` is now supported to debug the container. Volume maps and better compatibilty with ipv4/v6 by concurrent connection finding.
96
100
  * 1.1.0 - Use `fastled` as the command for the wasm compiler.
@@ -72,6 +72,10 @@ provide shims for most of the common api points.
72
72
 
73
73
  # Revisions
74
74
 
75
+ * 1.1.6 - Use the fast src volume map allow quick updates to fastled when developing on the source code.
76
+ * 1.1.5 - Filter out hidden files and directories from being included in the sketch archive sent to the compiler.
77
+ * 1.1.4 - Fix regression introduced by testing out ipv4/ipv6 connections from a thread pool.
78
+ * 1.1.3 - Live editing of *.h and *.cpp files is now possible. Sketch cache will be disabled in this mode.
75
79
  * 1.1.2 - `--server` will now volume map fastled src directory if it detects this. This was also implemented on the docker side.
76
80
  * 1.1.1 - `--interactive` is now supported to debug the container. Volume maps and better compatibilty with ipv4/v6 by concurrent connection finding.
77
81
  * 1.1.0 - Use `fastled` as the command for the wasm compiler.
@@ -19,7 +19,7 @@ dependencies = [
19
19
  "filelock",
20
20
  ]
21
21
  # Change this with the version number bump.
22
- version = "1.1.2"
22
+ version = "1.1.6"
23
23
 
24
24
  [tool.setuptools]
25
25
  package-dir = {"" = "src"}
@@ -20,6 +20,7 @@ from fastled.compile_server import CompileServer, looks_like_fastled_repo
20
20
  from fastled.docker_manager import DockerManager
21
21
  from fastled.filewatcher import FileChangedNotifier
22
22
  from fastled.open_browser import open_browser_thread
23
+ from fastled.sketch import get_sketch_files
23
24
  from fastled.web_compile import web_compile
24
25
 
25
26
  machine = platform.machine().lower()
@@ -159,9 +160,7 @@ def run_web_compiler(
159
160
 
160
161
  # now check to see if the hash value is the same as the last hash value
161
162
  if last_hash_value is not None and last_hash_value == web_result.hash_value:
162
- print(
163
- "\nNo significant source code changes detected and data was the same, skipping recompilation."
164
- )
163
+ print("\nSkipping redeploy: No significant changes found.")
165
164
  print_results()
166
165
  return CompiledResult(
167
166
  success=True, fastled_js=str(output_dir), hash_value=web_result.hash_value
@@ -212,12 +211,7 @@ def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServe
212
211
 
213
212
 
214
213
  def _lots_and_lots_of_files(directory: Path) -> bool:
215
- count = 0
216
- for root, dirs, files in os.walk(directory):
217
- count += len(files)
218
- if count > 100:
219
- return True
220
- return False
214
+ return len(get_sketch_files(directory)) > 100
221
215
 
222
216
 
223
217
  def _looks_like_sketch_directory(directory: Path) -> bool:
@@ -151,10 +151,14 @@ class CompileServer:
151
151
  f"Mounting FastLED source directory {self.fastled_src_dir} into container /js/fastled/src"
152
152
  )
153
153
  volumes = {
154
- str(self.fastled_src_dir): {"bind": "/js/fastled/src", "mode": "rw"}
154
+ str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
155
155
  }
156
156
  # no auto-update because the source directory is mapped in.
157
- server_command.append("--no-auto-update")
157
+ server_command.append("--no-auto-update") # stop git repo updates.
158
+ if not self.interactive:
159
+ server_command.append(
160
+ "--no-sketch-cache"
161
+ ) # Remove sketch cache which assumes src is static.
158
162
  self.running_process = self.docker.run_container(
159
163
  server_command, ports=ports, volumes=volumes
160
164
  )
@@ -0,0 +1,18 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+
5
+ def get_sketch_files(directory: Path) -> list[Path]:
6
+ files: list[Path] = []
7
+ for root, dirs, filenames in os.walk(directory):
8
+ # ignore hidden directories
9
+ dirs[:] = [d for d in dirs if not d.startswith(".")]
10
+ # ignore fastled_js directory
11
+ dirs[:] = [d for d in dirs if "fastled_js" not in d]
12
+ # ignore hidden files
13
+ filenames = [f for f in filenames if not f.startswith(".")]
14
+ for filename in filenames:
15
+ if "platformio.ini" in filename:
16
+ continue
17
+ files.append(Path(root) / filename)
18
+ return files
@@ -0,0 +1,227 @@
1
+ import io
2
+ import shutil
3
+ import tempfile
4
+ import zipfile
5
+ from concurrent.futures import ThreadPoolExecutor, as_completed
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ import httpx
10
+
11
+ from fastled.build_mode import BuildMode
12
+ from fastled.compile_server import SERVER_PORT
13
+ from fastled.sketch import get_sketch_files
14
+
15
+ DEFAULT_HOST = "https://fastled.onrender.com"
16
+ ENDPOINT_COMPILED_WASM = "compile/wasm"
17
+ _TIMEOUT = 60 * 4 # 2 mins timeout
18
+ _AUTH_TOKEN = "oBOT5jbsO4ztgrpNsQwlmFLIKB"
19
+
20
+ _THREAD_POOL = ThreadPoolExecutor(max_workers=8)
21
+
22
+
23
+ @dataclass
24
+ class TestConnectionResult:
25
+ host: str
26
+ success: bool
27
+ ipv4: bool
28
+
29
+
30
+ @dataclass
31
+ class WebCompileResult:
32
+ success: bool
33
+ stdout: str
34
+ hash_value: str | None
35
+ zip_bytes: bytes
36
+
37
+ def __bool__(self) -> bool:
38
+ return self.success
39
+
40
+
41
+ def _sanitize_host(host: str) -> str:
42
+ if host.startswith("http"):
43
+ return host
44
+ is_local_host = "localhost" in host or "127.0.0.1" in host or "0.0.0.0" in host
45
+ use_https = not is_local_host
46
+ if use_https:
47
+ return host if host.startswith("https://") else f"https://{host}"
48
+ return host if host.startswith("http://") else f"http://{host}"
49
+
50
+
51
+ _CONNECTION_ERROR_MAP: dict[str, TestConnectionResult] = {}
52
+
53
+
54
+ def _test_connection(host: str, use_ipv4: bool) -> TestConnectionResult:
55
+ key = f"{host}-{use_ipv4}"
56
+ maybe_result: TestConnectionResult | None = _CONNECTION_ERROR_MAP.get(key)
57
+ if maybe_result is not None:
58
+ return maybe_result
59
+ transport = httpx.HTTPTransport(local_address="0.0.0.0") if use_ipv4 else None
60
+ try:
61
+ with httpx.Client(
62
+ timeout=_TIMEOUT,
63
+ transport=transport,
64
+ ) as test_client:
65
+ test_response = test_client.get(
66
+ f"{host}/healthz", timeout=3, follow_redirects=True
67
+ )
68
+ result = TestConnectionResult(
69
+ host, test_response.status_code == 200, use_ipv4
70
+ )
71
+ _CONNECTION_ERROR_MAP[key] = result
72
+ except Exception:
73
+ result = TestConnectionResult(host, False, use_ipv4)
74
+ _CONNECTION_ERROR_MAP[key] = result
75
+ return result
76
+
77
+
78
+ def zip_files(directory: Path) -> bytes | Exception:
79
+ print("Zipping files...")
80
+ try:
81
+ files = get_sketch_files(directory)
82
+ if not files:
83
+ raise FileNotFoundError(f"No files found in {directory}")
84
+ for f in files:
85
+ print(f"Adding file: {f}")
86
+ # Create in-memory zip file
87
+ zip_buffer = io.BytesIO()
88
+ with zipfile.ZipFile(
89
+ zip_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
90
+ ) as zip_file:
91
+ for file_path in files:
92
+ relative_path = file_path.relative_to(directory)
93
+ zip_file.write(file_path, str(Path("wasm") / relative_path))
94
+ return zip_buffer.getvalue()
95
+ except Exception as e:
96
+ return e
97
+
98
+
99
+ def web_compile(
100
+ directory: Path,
101
+ host: str | None = None,
102
+ auth_token: str | None = None,
103
+ build_mode: BuildMode | None = None,
104
+ profile: bool = False,
105
+ ) -> WebCompileResult:
106
+ host = _sanitize_host(host or DEFAULT_HOST)
107
+ print("Compiling on", host)
108
+ auth_token = auth_token or _AUTH_TOKEN
109
+
110
+ if not directory.exists():
111
+ raise FileNotFoundError(f"Directory not found: {directory}")
112
+
113
+ zip_bytes = zip_files(directory)
114
+ if isinstance(zip_bytes, Exception):
115
+ return WebCompileResult(
116
+ success=False, stdout=str(zip_bytes), hash_value=None, zip_bytes=b""
117
+ )
118
+ archive_size = len(zip_bytes)
119
+ print(f"Web compiling on {host}...")
120
+ try:
121
+
122
+ files = {"file": ("wasm.zip", zip_bytes, "application/x-zip-compressed")}
123
+ urls = [host]
124
+ domain = host.split("://")[-1]
125
+ if ":" not in domain:
126
+ urls.append(f"{host}:{SERVER_PORT}")
127
+ test_connection_result: TestConnectionResult | None = None
128
+
129
+ futures: list = []
130
+ ip_versions = [True, False] if "localhost" not in host else [True]
131
+ for ipv4 in ip_versions:
132
+ for url in urls:
133
+ f = _THREAD_POOL.submit(_test_connection, url, ipv4)
134
+ futures.append(f)
135
+
136
+ succeeded = False
137
+ for future in as_completed(futures):
138
+ result: TestConnectionResult = future.result()
139
+
140
+ if result.success:
141
+ print(f"Connection successful to {result.host}")
142
+ succeeded = True
143
+ # host = test_url
144
+ test_connection_result = result
145
+ break
146
+ else:
147
+ print(f"Ignoring {result.host} due to connection failure")
148
+
149
+ if not succeeded:
150
+ print("Connection failed to all endpoints")
151
+ return WebCompileResult(
152
+ success=False,
153
+ stdout="Connection failed",
154
+ hash_value=None,
155
+ zip_bytes=b"",
156
+ )
157
+ assert test_connection_result is not None
158
+ ipv4_stmt = "IPv4" if test_connection_result.ipv4 else "IPv6"
159
+ transport = (
160
+ httpx.HTTPTransport(local_address="0.0.0.0")
161
+ if test_connection_result.ipv4
162
+ else None
163
+ )
164
+ with httpx.Client(
165
+ transport=transport,
166
+ timeout=_TIMEOUT,
167
+ ) as client:
168
+ headers = {
169
+ "accept": "application/json",
170
+ "authorization": auth_token,
171
+ "build": (
172
+ build_mode.value.lower()
173
+ if build_mode
174
+ else BuildMode.QUICK.value.lower()
175
+ ),
176
+ "profile": "true" if profile else "false",
177
+ }
178
+
179
+ url = f"{test_connection_result.host}/{ENDPOINT_COMPILED_WASM}"
180
+ print(f"Compiling on {url} via {ipv4_stmt}. Zip size: {archive_size} bytes")
181
+ response = client.post(
182
+ url,
183
+ follow_redirects=True,
184
+ files=files,
185
+ headers=headers,
186
+ timeout=_TIMEOUT,
187
+ )
188
+
189
+ if response.status_code != 200:
190
+ json_response = response.json()
191
+ detail = json_response.get("detail", "Could not compile")
192
+ return WebCompileResult(
193
+ success=False, stdout=detail, hash_value=None, zip_bytes=b""
194
+ )
195
+
196
+ print(f"Response status code: {response}")
197
+ # Create a temporary directory to extract the zip
198
+ with tempfile.TemporaryDirectory() as extract_dir:
199
+ extract_path = Path(extract_dir)
200
+
201
+ # Write the response content to a temporary zip file
202
+ temp_zip = extract_path / "response.zip"
203
+ temp_zip.write_bytes(response.content)
204
+
205
+ # Extract the zip
206
+ shutil.unpack_archive(temp_zip, extract_path, "zip")
207
+
208
+ # Read stdout from out.txt if it exists
209
+ stdout_file = extract_path / "out.txt"
210
+ hash_file = extract_path / "hash.txt"
211
+ stdout = stdout_file.read_text() if stdout_file.exists() else ""
212
+ hash_value = hash_file.read_text() if hash_file.exists() else None
213
+
214
+ return WebCompileResult(
215
+ success=True,
216
+ stdout=stdout,
217
+ hash_value=hash_value,
218
+ zip_bytes=response.content,
219
+ )
220
+ except KeyboardInterrupt:
221
+ print("Keyboard interrupt")
222
+ raise
223
+ except httpx.HTTPError as e:
224
+ print(f"Error: {e}")
225
+ return WebCompileResult(
226
+ success=False, stdout=str(e), hash_value=None, zip_bytes=b""
227
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastled
3
- Version: 1.1.2
3
+ Version: 1.1.6
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -91,6 +91,10 @@ provide shims for most of the common api points.
91
91
 
92
92
  # Revisions
93
93
 
94
+ * 1.1.6 - Use the fast src volume map allow quick updates to fastled when developing on the source code.
95
+ * 1.1.5 - Filter out hidden files and directories from being included in the sketch archive sent to the compiler.
96
+ * 1.1.4 - Fix regression introduced by testing out ipv4/ipv6 connections from a thread pool.
97
+ * 1.1.3 - Live editing of *.h and *.cpp files is now possible. Sketch cache will be disabled in this mode.
94
98
  * 1.1.2 - `--server` will now volume map fastled src directory if it detects this. This was also implemented on the docker side.
95
99
  * 1.1.1 - `--interactive` is now supported to debug the container. Volume maps and better compatibilty with ipv4/v6 by concurrent connection finding.
96
100
  * 1.1.0 - Use `fastled` as the command for the wasm compiler.
@@ -70,6 +70,7 @@ src/fastled/docker_manager.py
70
70
  src/fastled/filewatcher.py
71
71
  src/fastled/open_browser.py
72
72
  src/fastled/paths.py
73
+ src/fastled/sketch.py
73
74
  src/fastled/web_compile.py
74
75
  src/fastled.egg-info/PKG-INFO
75
76
  src/fastled.egg-info/SOURCES.txt
@@ -1,220 +0,0 @@
1
- import shutil
2
- import tempfile
3
- from concurrent.futures import ThreadPoolExecutor, as_completed
4
- from dataclasses import dataclass
5
- from pathlib import Path
6
-
7
- import httpx
8
-
9
- from fastled.build_mode import BuildMode
10
- from fastled.compile_server import SERVER_PORT
11
-
12
- DEFAULT_HOST = "https://fastled.onrender.com"
13
- ENDPOINT_COMPILED_WASM = "compile/wasm"
14
- _TIMEOUT = 60 * 4 # 2 mins timeout
15
- _AUTH_TOKEN = "oBOT5jbsO4ztgrpNsQwlmFLIKB"
16
-
17
-
18
- @dataclass
19
- class TestConnectionResult:
20
- host: str
21
- success: bool
22
- ipv4: bool
23
-
24
-
25
- @dataclass
26
- class WebCompileResult:
27
- success: bool
28
- stdout: str
29
- hash_value: str | None
30
- zip_bytes: bytes
31
-
32
- def __bool__(self) -> bool:
33
- return self.success
34
-
35
-
36
- def _sanitize_host(host: str) -> str:
37
- if host.startswith("http"):
38
- return host
39
- is_local_host = "localhost" in host or "127.0.0.1" in host or "0.0.0.0" in host
40
- use_https = not is_local_host
41
- if use_https:
42
- return host if host.startswith("https://") else f"https://{host}"
43
- return host if host.startswith("http://") else f"http://{host}"
44
-
45
-
46
- _CONNECTION_ERROR_MAP: dict[str, TestConnectionResult] = {}
47
-
48
-
49
- def _test_connection(host: str, use_ipv4: bool) -> TestConnectionResult:
50
- key = f"{host}-{use_ipv4}"
51
- maybe_result: TestConnectionResult | None = _CONNECTION_ERROR_MAP.get(key)
52
- if maybe_result is not None:
53
- return maybe_result
54
- transport = httpx.HTTPTransport(local_address="0.0.0.0") if use_ipv4 else None
55
- try:
56
- with httpx.Client(
57
- timeout=_TIMEOUT,
58
- transport=transport,
59
- ) as test_client:
60
- test_response = test_client.get(
61
- f"{host}/healthz", timeout=_TIMEOUT, follow_redirects=True
62
- )
63
- result = TestConnectionResult(
64
- host, test_response.status_code == 200, use_ipv4
65
- )
66
- _CONNECTION_ERROR_MAP[key] = result
67
- except Exception:
68
- result = TestConnectionResult(host, False, use_ipv4)
69
- _CONNECTION_ERROR_MAP[key] = result
70
- return result
71
-
72
-
73
- def web_compile(
74
- directory: Path,
75
- host: str | None = None,
76
- auth_token: str | None = None,
77
- build_mode: BuildMode | None = None,
78
- profile: bool = False,
79
- ) -> WebCompileResult:
80
- host = _sanitize_host(host or DEFAULT_HOST)
81
- print("Compiling on", host)
82
- auth_token = auth_token or _AUTH_TOKEN
83
- # zip up the files
84
- print("Zipping files...")
85
-
86
- # Create a temporary zip file
87
- with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_zip:
88
- # Create temporary directory for organizing files
89
- with tempfile.TemporaryDirectory() as temp_dir:
90
- # Create wasm subdirectory
91
- wasm_dir = Path(temp_dir) / "wasm"
92
-
93
- # Copy all files from source to wasm subdirectory, excluding fastled_js
94
- def ignore_fastled_js(dir: str, files: list[str]) -> list[str]:
95
- if "fastled_js" in dir:
96
- return files
97
- if dir.startswith("."):
98
- return files
99
- return []
100
-
101
- shutil.copytree(directory, wasm_dir, ignore=ignore_fastled_js)
102
- # Create zip archive from the temp directory
103
- shutil.make_archive(tmp_zip.name[:-4], "zip", temp_dir)
104
-
105
- print(f"Web compiling on {host}...")
106
-
107
- try:
108
- with open(tmp_zip.name, "rb") as zip_file:
109
- files = {"file": ("wasm.zip", zip_file, "application/x-zip-compressed")}
110
- urls = [host]
111
- domain = host.split("://")[-1]
112
- if ":" not in domain:
113
- urls.append(f"{host}:{SERVER_PORT}")
114
- test_connection_result: TestConnectionResult | None = None
115
-
116
- with ThreadPoolExecutor(max_workers=len(urls)) as executor:
117
- futures: list = []
118
- ip_versions = [True, False] if "localhost" not in host else [True]
119
- for ipv4 in ip_versions:
120
- for url in urls:
121
- f = executor.submit(_test_connection, url, ipv4)
122
- futures.append(f)
123
-
124
- succeeded = False
125
- for future in as_completed(futures):
126
- result: TestConnectionResult = future.result()
127
-
128
- if result.success:
129
- print(f"Connection successful to {result.host}")
130
- succeeded = True
131
- # host = test_url
132
- test_connection_result = result
133
- break
134
- else:
135
- print(f"Ignoring {result.host} due to connection failure")
136
-
137
- if not succeeded:
138
- print("Connection failed to all endpoints")
139
- return WebCompileResult(
140
- success=False,
141
- stdout="Connection failed",
142
- hash_value=None,
143
- zip_bytes=b"",
144
- )
145
- assert test_connection_result is not None
146
- ipv4_stmt = "IPv4" if test_connection_result.ipv4 else "IPv6"
147
- transport = (
148
- httpx.HTTPTransport(local_address="0.0.0.0")
149
- if test_connection_result.ipv4
150
- else None
151
- )
152
- with httpx.Client(
153
- transport=transport,
154
- timeout=_TIMEOUT,
155
- ) as client:
156
- headers = {
157
- "accept": "application/json",
158
- "authorization": auth_token,
159
- "build": (
160
- build_mode.value.lower()
161
- if build_mode
162
- else BuildMode.QUICK.value.lower()
163
- ),
164
- "profile": "true" if profile else "false",
165
- }
166
-
167
- url = f"{test_connection_result.host}/{ENDPOINT_COMPILED_WASM}"
168
- print(f"Compiling on {url} via {ipv4_stmt}")
169
- response = client.post(
170
- url,
171
- follow_redirects=True,
172
- files=files,
173
- headers=headers,
174
- timeout=_TIMEOUT,
175
- )
176
-
177
- if response.status_code != 200:
178
- json_response = response.json()
179
- detail = json_response.get("detail", "Could not compile")
180
- return WebCompileResult(
181
- success=False, stdout=detail, hash_value=None, zip_bytes=b""
182
- )
183
-
184
- print(f"Response status code: {response}")
185
- # Create a temporary directory to extract the zip
186
- with tempfile.TemporaryDirectory() as extract_dir:
187
- extract_path = Path(extract_dir)
188
-
189
- # Write the response content to a temporary zip file
190
- temp_zip = extract_path / "response.zip"
191
- temp_zip.write_bytes(response.content)
192
-
193
- # Extract the zip
194
- shutil.unpack_archive(temp_zip, extract_path, "zip")
195
-
196
- # Read stdout from out.txt if it exists
197
- stdout_file = extract_path / "out.txt"
198
- hash_file = extract_path / "hash.txt"
199
- stdout = stdout_file.read_text() if stdout_file.exists() else ""
200
- hash_value = hash_file.read_text() if hash_file.exists() else None
201
-
202
- return WebCompileResult(
203
- success=True,
204
- stdout=stdout,
205
- hash_value=hash_value,
206
- zip_bytes=response.content,
207
- )
208
- except KeyboardInterrupt:
209
- print("Keyboard interrupt")
210
- raise
211
- except httpx.HTTPError as e:
212
- print(f"Error: {e}")
213
- return WebCompileResult(
214
- success=False, stdout=str(e), hash_value=None, zip_bytes=b""
215
- )
216
- finally:
217
- try:
218
- Path(tmp_zip.name).unlink()
219
- except PermissionError:
220
- print("Warning: Could not delete temporary zip file")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes