Skip to content

Commit cdbfc63

Browse files
Rune Daugaard Harlykruneharlyk
Rune Daugaard Harlyk
authored andcommitted
Simpifies build script
1 parent 2db9958 commit cdbfc63

File tree

1 file changed

+111
-136
lines changed

1 file changed

+111
-136
lines changed

scripts/build_interface.py

+111-136
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
# Copyright (C) 2018 - 2023 rjwats
88
# Copyright (C) 2023 theelims
99
# Copyright (C) 2023 Maxtrium B.V. [ code available under dual license ]
10+
# Copyright (C) 2024 runeharlyk
1011
#
1112
# All Rights Reserved. This software may be modified and distributed under
1213
# the terms of the LGPL v3 license. See the LICENSE file for details.
1314

1415
from pathlib import Path
1516
from shutil import copytree, rmtree, copyfileobj
16-
from subprocess import check_output, Popen, PIPE, STDOUT, CalledProcessError
17-
from os.path import exists
18-
from typing import Final
17+
from os.path import exists, getmtime
1918
import os
2019
import gzip
2120
import mimetypes
@@ -24,161 +23,137 @@
2423

2524
Import("env")
2625

27-
# print("Current build environment:")
28-
# print(env.ParseFlags(env["BUILD_FLAGS"]).get("CPPDEFINES"))
26+
project_dir = env["PROJECT_DIR"]
27+
buildFlags = env.ParseFlags(env["BUILD_FLAGS"])
2928

30-
# print("Current CLI targets", COMMAND_LINE_TARGETS)
31-
# print("Current Build targets", BUILD_TARGETS)
29+
interface_dir = project_dir + "/interface"
30+
output_file = project_dir + "/lib/framework/WWWData.h"
31+
source_www_dir = interface_dir + "/src"
32+
build_dir = interface_dir + "/build"
33+
filesystem_dir = project_dir + "/data/www"
3234

33-
OUTPUTFILE: Final[str] = env["PROJECT_DIR"] + "/lib/framework/WWWData.h"
34-
SOURCEWWWDIR: Final[str] = env["PROJECT_DIR"] + "/interface/src"
3535

36-
def OutputFileExits():
37-
return os.path.exists(OUTPUTFILE)
36+
def find_latest_timestamp_for_app():
37+
return max(
38+
(getmtime(f) for f in glob.glob(f"{source_www_dir}/**/*", recursive=True))
39+
)
3840

39-
def findLastestTimeStampWWWInterface():
40-
list_of_files = glob.glob(SOURCEWWWDIR+'/**/*', recursive=True)
41-
# print(list_of_files)
42-
latest_file = max(list_of_files, key=os.path.getmtime)
43-
# print(latest_file)
4441

45-
return os.path.getmtime(latest_file)
42+
def should_regenerate_output_file():
43+
if not flag_exists("EMBED_WWW") or not exists(output_file):
44+
return True
45+
last_source_change = find_latest_timestamp_for_app()
46+
last_build = getmtime(output_file)
4647

47-
def findTimestampOutputFile():
48-
return os.path.getmtime(OUTPUTFILE)
48+
print(
49+
f"Newest file: {datetime.fromtimestamp(last_source_change)}, output file: {datetime.fromtimestamp(last_build)}"
50+
)
4951

50-
def needtoRegenerateOutputFile():
51-
if not flagExists("EMBED_WWW"):
52-
return True
53-
else:
54-
if (OutputFileExits()):
55-
latestWWWInterface = findLastestTimeStampWWWInterface()
56-
timestampOutputFile = findTimestampOutputFile()
57-
58-
# print timestamp of newest file in interface directory and timestamp of outputfile nicely formatted as time
59-
print(f'Newest interface file: {datetime.fromtimestamp(latestWWWInterface):%Y-%m-%d %H:%M:%S}, WWW Outputfile: {datetime.fromtimestamp(timestampOutputFile):%Y-%m-%d %H:%M:%S}')
60-
# print(f'Newest interface file: {latestWWWInterface:.2f}, WWW Outputfile: {timestampOutputFile:.2f}')
61-
62-
sourceEdited=( timestampOutputFile < latestWWWInterface )
63-
if (sourceEdited):
64-
print("Svelte source files are updated. Need to regenerate.")
65-
return True
66-
else:
67-
print("Current outputfile is O.K. No need to regenerate.")
68-
return False
69-
70-
else:
71-
print("WWW outputfile does not exists. Need to regenerate.")
72-
return True
52+
return last_build < last_source_change
7353

74-
def gzipFile(file):
54+
55+
def gzip_file(file):
7556
with open(file, 'rb') as f_in:
7657
with gzip.open(file + '.gz', 'wb') as f_out:
7758
copyfileobj(f_in, f_out)
7859
os.remove(file)
7960

80-
def flagExists(flag):
81-
buildFlags = env.ParseFlags(env["BUILD_FLAGS"])
61+
62+
def flag_exists(flag):
8263
for define in buildFlags.get("CPPDEFINES"):
8364
if (define == flag or (isinstance(define, list) and define[0] == flag)):
8465
return True
66+
return False
8567

86-
def buildProgMem():
87-
mimetypes.init()
88-
progmem = open(OUTPUTFILE,"w")
89-
90-
progmem.write("#include <functional>\n")
91-
progmem.write("#include <Arduino.h>\n")
92-
93-
94-
progmemCounter = 0
95-
96-
assetMap = {}
97-
98-
# Iterate over all files in the build directory
99-
for path in Path("build").rglob("*.*"):
100-
asset_path = path.relative_to("build").as_posix()
101-
print("Converting " + str(asset_path))
102-
103-
asset_var = 'ESP_SVELTEKIT_DATA_' + str(progmemCounter)
104-
asset_mime = mimetypes.types_map['.' + asset_path.split('.')[-1]]
105-
106-
progmem.write('// ' + str(asset_path) + '\n')
107-
progmem.write('const uint8_t ' + asset_var + '[] = {\n ')
108-
progmemCounter += 1
109-
110-
# Open path as binary file, compress and read into byte array
111-
size = 0
112-
with open(path, "rb") as f:
113-
zipBuffer = gzip.compress(f.read())
114-
for byte in zipBuffer:
115-
if not (size % 16):
116-
progmem.write('\n ')
117-
118-
progmem.write(f"0x{byte:02X}" + ',')
119-
size += 1
120-
121-
progmem.write('\n};\n\n')
122-
assetMap[asset_path] = { "name": asset_var, "mime": asset_mime, "size": size }
123-
124-
progmem.write('typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;\n\n')
125-
progmem.write('class WWWData {\n')
126-
progmem.write(' public:\n')
127-
progmem.write(' static void registerRoutes(RouteRegistrationHandler handler) {\n')
128-
129-
for asset_path, asset in assetMap.items():
130-
progmem.write(' handler("/' + str(asset_path) + '", "' + asset['mime'] + '", ' + asset['name'] + ', ' + str(asset['size']) + ');\n')
131-
132-
progmem.write(' }\n')
133-
progmem.write('};\n')
134-
135-
progmem.write('\n')
136-
137-
138-
def buildWeb():
139-
os.chdir("interface")
140-
print("Building interface with npm")
141-
try:
142-
env.Execute("npm install")
143-
env.Execute("npm run build")
144-
buildPath = Path("build")
145-
wwwPath = Path("../data/www")
146-
if not flagExists("EMBED_WWW"):
147-
if wwwPath.exists() and wwwPath.is_dir():
148-
rmtree(wwwPath)
149-
print("Copying and compress interface to data directory")
150-
copytree(buildPath, wwwPath)
151-
for currentpath, folders, files in os.walk(wwwPath):
152-
for file in files:
153-
gzipFile(os.path.join(currentpath, file))
154-
else:
155-
print("Converting interface to PROGMEM")
156-
buildProgMem()
157-
158-
finally:
159-
os.chdir("..")
160-
if not flagExists("EMBED_WWW"):
161-
print("Build LittleFS file system image and upload to ESP32")
162-
env.Execute("pio run --target uploadfs")
16368

164-
print("running: build_interface.py")
69+
def get_package_manager():
70+
if exists(os.path.join(interface_dir, "pnpm-lock.yaml")):
71+
return "pnpm"
72+
return "npm"
16573

166-
# Dump global construction environment (for debug purpose)
167-
#print(env.Dump())
16874

169-
# Dump project construction environment (for debug purpose)
170-
#print(projenv.Dump())
75+
def build_webapp():
76+
package_manager = get_package_manager()
77+
print(f"Building interface with {package_manager}")
78+
os.chdir(interface_dir)
79+
env.Execute(f"{package_manager} install")
80+
env.Execute(f"{package_manager} run build")
81+
os.chdir("..")
17182

172-
if (needtoRegenerateOutputFile()):
173-
buildWeb()
17483

175-
#env.AddPreAction("${BUILD_DIR}/src/HTTPServer.o", buildWebInterface)
84+
def embed_webapp():
85+
if flag_exists("EMBED_WWW"):
86+
print("Converting interface to PROGMEM")
87+
build_progmem()
88+
return
89+
add_app_to_filesystem()
17690

177-
# if ("upload" in BUILD_TARGETS):
178-
# print(BUILD_TARGETS)
179-
# if (needtoRegenerateOutputFile()):
180-
# buildWeb()
181-
# else:
182-
# print("Skipping build interface step for target(s): " + ", ".join(BUILD_TARGETS))
18391

92+
def build_progmem():
93+
mimetypes.init()
94+
with open(output_file, "w") as progmem:
95+
progmem.write("#include <functional>\n")
96+
progmem.write("#include <Arduino.h>\n")
97+
98+
assetMap = {}
99+
100+
for idx, path in enumerate(Path(build_dir).rglob("*.*")):
101+
asset_path = path.relative_to(build_dir).as_posix()
102+
asset_mime = (
103+
mimetypes.guess_type(asset_path)[0] or "application/octet-stream"
104+
)
105+
print(f"Converting {asset_path}")
106+
107+
asset_var = f"ESP_SVELTEKIT_DATA_{idx}"
108+
progmem.write(f"// {asset_path}\n")
109+
progmem.write(f"const uint8_t {asset_var}[] = {{\n ")
110+
file_data = gzip.compress(path.read_bytes())
111+
112+
for i, byte in enumerate(file_data):
113+
if i and not (i % 16):
114+
progmem.write("\n\t")
115+
progmem.write(f"0x{byte:02X},")
116+
117+
progmem.write("\n};\n\n")
118+
assetMap[asset_path] = {
119+
"name": asset_var,
120+
"mime": asset_mime,
121+
"size": len(file_data),
122+
}
123+
124+
progmem.write(
125+
"typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;\n\n"
126+
)
127+
progmem.write("class WWWData {\n")
128+
progmem.write("\tpublic:\n")
129+
progmem.write(
130+
"\t\tstatic void registerRoutes(RouteRegistrationHandler handler) {\n"
131+
)
132+
133+
for asset_path, asset in assetMap.items():
134+
progmem.write(
135+
f'\t\t\thandler("/{asset_path}", "{asset["mime"]}", {asset["name"]}, {asset["size"]});\n'
136+
)
137+
138+
progmem.write("\t\t}\n")
139+
progmem.write("};\n\n")
140+
141+
142+
def add_app_to_filesystem():
143+
build_path = Path(build_dir)
144+
www_path = Path(filesystem_dir)
145+
if www_path.exists() and www_path.is_dir():
146+
rmtree(www_path)
147+
print("Copying and compress interface to data directory")
148+
copytree(build_path, www_path)
149+
for current_path, _, files in os.walk(www_path):
150+
for file in files:
151+
gzip_file(os.path.join(current_path, file))
152+
print("Build LittleFS file system image and upload to ESP32")
153+
env.Execute("pio run --target uploadfs")
184154

155+
156+
print("running: build_interface.py")
157+
if should_regenerate_output_file():
158+
build_webapp()
159+
embed_webapp()

0 commit comments

Comments
 (0)