maestro-tool 0.1.0__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.
Files changed (44) hide show
  1. maestro/__init__.py +0 -0
  2. maestro/assets/__init__.py +0 -0
  3. maestro/assets/containers/yocto.def +128 -0
  4. maestro/assets/tcl/jtag_axi.tcl +73 -0
  5. maestro/assets/tcl/package_verilog_ip.tcl +151 -0
  6. maestro/assets/tcl/program.tcl +65 -0
  7. maestro/assets/verible/format.flags +27 -0
  8. maestro/assets/verible/lint.rules +8 -0
  9. maestro/assets/vitis/rpu_build.py +90 -0
  10. maestro/assets/vitis/rpu_create.py +165 -0
  11. maestro/assets/vitis/rpu_setup.py +84 -0
  12. maestro/cli/__init__.py +11 -0
  13. maestro/cli/dispatcher.py +301 -0
  14. maestro/commands/__init__.py +27 -0
  15. maestro/commands/base.py +115 -0
  16. maestro/commands/bender.py +44 -0
  17. maestro/commands/embgen.py +75 -0
  18. maestro/commands/git.py +147 -0
  19. maestro/commands/hog.py +285 -0
  20. maestro/commands/media.py +129 -0
  21. maestro/commands/verible.py +118 -0
  22. maestro/commands/vitis.py +317 -0
  23. maestro/commands/yocto.py +232 -0
  24. maestro/config_global.py +117 -0
  25. maestro/config_models.py +85 -0
  26. maestro/discover.py +66 -0
  27. maestro/executors/__init__.py +21 -0
  28. maestro/executors/apptainer.py +175 -0
  29. maestro/executors/base.py +269 -0
  30. maestro/executors/bash.py +27 -0
  31. maestro/executors/docker.py +105 -0
  32. maestro/executors/prefix.py +26 -0
  33. maestro/executors/uv.py +26 -0
  34. maestro/logging.py +61 -0
  35. maestro/models.py +43 -0
  36. maestro/paths.py +18 -0
  37. maestro/tools/__init__.py +0 -0
  38. maestro/tools/depends.py +34 -0
  39. maestro_tool-0.1.0.dist-info/METADATA +130 -0
  40. maestro_tool-0.1.0.dist-info/RECORD +44 -0
  41. maestro_tool-0.1.0.dist-info/WHEEL +4 -0
  42. maestro_tool-0.1.0.dist-info/entry_points.txt +5 -0
  43. maestro_tool-0.1.0.dist-info/licenses/LICENSES/Apache-2.0.txt +73 -0
  44. maestro_tool-0.1.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt +156 -0
maestro/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,128 @@
1
+ # SPDX-FileCopyrightText: 2026 Maestro authors and contributors
2
+ # SPDX-FileContributor: Cedric Hirschi <cedr02@live.com>
3
+ # SPDX-FileContributor: Tim Frey <timfreyb@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ Bootstrap: docker
8
+ From: ubuntu:24.04
9
+
10
+ %post
11
+ set -eux
12
+
13
+ # Remove default ubuntu user created by Ubuntu 24.04 base image
14
+ touch /var/mail/ubuntu && chown ubuntu /var/mail/ubuntu && userdel -r ubuntu || true
15
+
16
+ export DEBIAN_FRONTEND=noninteractive
17
+
18
+ # Use bash as /bin/sh (required by Yocto)
19
+ echo "dash dash/sh boolean false" | debconf-set-selections
20
+ dpkg-reconfigure dash
21
+
22
+ # libtinfo5 is needed by Xilinx tools but not available in Ubuntu 24.04 repos
23
+ # Add Ubuntu 22.04 (jammy) universe repo
24
+ cat >/etc/apt/sources.list.d/jammy-universe.list <<'EOF'
25
+ deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy universe
26
+ deb [arch=amd64] http://security.ubuntu.com/ubuntu jammy-security universe
27
+ deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-updates universe
28
+ EOF
29
+
30
+ # Pin libtinfo5 to jammy version
31
+ cat >/etc/apt/preferences.d/pin-jammy-libtinfo5 <<'EOF'
32
+ Package: *
33
+ Pin: release n=jammy
34
+ Pin-Priority: -10
35
+
36
+ Package: libtinfo5
37
+ Pin: release n=jammy
38
+ Pin-Priority: 990
39
+ EOF
40
+
41
+ apt-get update && apt-get install -y \
42
+ locales-all \
43
+ locales \
44
+ tofrodos \
45
+ iproute2 \
46
+ gawk \
47
+ xvfb \
48
+ git \
49
+ make \
50
+ net-tools \
51
+ libncurses5-dev \
52
+ update-inetd \
53
+ tftpd-hpa \
54
+ zlib1g-dev \
55
+ libssl-dev \
56
+ flex \
57
+ bison \
58
+ libselinux1 \
59
+ gnupg \
60
+ wget \
61
+ diffstat \
62
+ chrpath \
63
+ socat \
64
+ xterm \
65
+ autoconf \
66
+ libtool \
67
+ libtool-bin \
68
+ unzip \
69
+ texinfo \
70
+ gcc-multilib \
71
+ build-essential \
72
+ libsdl1.2-dev \
73
+ libglib2.0-dev \
74
+ screen \
75
+ pax \
76
+ gzip \
77
+ python3-gi \
78
+ less \
79
+ lsb-release \
80
+ fakeroot \
81
+ libgtk2.0-0 \
82
+ libgtk2.0-dev \
83
+ cpio \
84
+ rsync \
85
+ xorg \
86
+ expect \
87
+ dos2unix \
88
+ bc \
89
+ zlib1g \
90
+ libusb-dev
91
+
92
+ wget http://archive.ubuntu.com/ubuntu/pool/main/t/tar/tar_1.34+dfsg-1build3_amd64.deb \
93
+ && dpkg -i tar_1.34+dfsg-1build3_amd64.deb \
94
+ && apt-mark hold tar \
95
+ && rm tar_1.34+dfsg-1build3_amd64.deb
96
+
97
+ apt-get install -y \
98
+ google-perftools \
99
+ default-jre
100
+
101
+ apt-get install -y \
102
+ gawk wget git diffstat unzip texinfo \
103
+ gcc build-essential chrpath socat cpio \
104
+ python3 python3-pip python3-pexpect xz-utils \
105
+ debianutils iputils-ping python3-git python3-jinja2 \
106
+ libegl1-mesa-dev libsdl1.2-dev xterm python3-subunit \
107
+ mesa-common-dev zstd liblz4-tool lz4 \
108
+ repo
109
+
110
+ apt-get install -y --no-install-recommends libtinfo5
111
+
112
+ # Set locale
113
+ env LANG=en_US.UTF-8 locale-gen --purge en_US.UTF-8
114
+ echo -e 'LANG="en_US.UTF-8"\nLANGUAGE="en_US:en"\n' > /etc/default/locale
115
+
116
+ apt-get clean
117
+
118
+ %environment
119
+ export LANG=en_US.UTF-8
120
+ export LANGUAGE=en_US:en
121
+ export LC_ALL=en_US.UTF-8
122
+
123
+ %labels
124
+ Description Yocto 5.0 / Xilinx 2025.2 build environment
125
+
126
+ %help
127
+ Yocto build environment for Xilinx ZynqMP targets.
128
+ Use run_apptainer.sh to launch an interactive shell with the correct bind mounts.
@@ -0,0 +1,73 @@
1
+ # SPDX-FileCopyrightText: 2026 Maestro authors and contributors
2
+ # SPDX-FileContributor: Cedric Hirschi <cedr02@live.com>
3
+ # SPDX-FileContributor: Tim Frey <timfreyb@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ # JTAG AXI batch. tclargs: <device> <hw_axi> <read|write> <addr_hex> ...
8
+ # read <addr>
9
+ # write <addr> <data>
10
+ # Prints "AXI_READ <hex>" after each read.
11
+ # On a transaction error whose text contains "failed" or "timeout", exits with an error.
12
+
13
+ set device [lindex $argv 0]
14
+ if {$device eq ""} { set device xck26_0 }
15
+
16
+ set hw_axi_name [lindex $argv 1]
17
+ if {$hw_axi_name eq ""} { set hw_axi_name hw_axi_1}
18
+
19
+ open_hw_manager
20
+ connect_hw_server
21
+ open_hw_target
22
+
23
+ current_hw_device [get_hw_devices $device]
24
+ refresh_hw_device [lindex [get_hw_devices $device] 0]
25
+
26
+ set hw_axi [get_hw_axis $hw_axi_name]
27
+ reset_hw_axi $hw_axi
28
+
29
+ set i 2
30
+ set n 0
31
+ set order {}
32
+
33
+ while {$i < [llength $argv]} {
34
+ set op [string tolower [lindex $argv $i]]
35
+ incr i
36
+ set addr [format %08x [scan [lindex $argv $i] %x]]
37
+ incr i
38
+
39
+ set name t$n
40
+ incr n
41
+
42
+ if {$op eq "read"} {
43
+ create_hw_axi_txn $name $hw_axi -type read -address $addr
44
+ } elseif {$op eq "write"} {
45
+ set data [lindex $argv $i]
46
+ incr i
47
+ create_hw_axi_txn $name $hw_axi -type write -address $addr -data $data
48
+ } else {
49
+ error "expected read or write, got: $op"
50
+ }
51
+ lappend order $name
52
+ }
53
+
54
+ foreach name $order {
55
+ set txn [get_hw_axi_txns $name]
56
+ if {[catch {run_hw_axi $txn} msg]} {
57
+ set detail "$msg\n$::errorInfo"
58
+ catch {close_hw_target}
59
+ # An unclocked or unreleased port shows up as "failed" or "timeout".
60
+ if {[string match -nocase *failed* $detail]
61
+ || [string match -nocase *timeout* $detail]} {
62
+ set addr ""
63
+ catch {set addr [get_property ADDRESS $txn]}
64
+ error "AXI transaction at $addr failed: clock or reset may not be configured correctly"
65
+ }
66
+ error $msg
67
+ }
68
+ if {[string tolower [get_property TYPE $txn]] eq "read"} {
69
+ puts "AXI_READ [get_property DATA $txn]"
70
+ }
71
+ }
72
+
73
+ close_hw_target
@@ -0,0 +1,151 @@
1
+ # SPDX-FileCopyrightText: 2026 Maestro authors and contributors
2
+ # SPDX-FileContributor: Cedric Hirschi <cedr02@live.com>
3
+ # SPDX-FileContributor: Tim Frey <timfreyb@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ # Package a Verilog/SystemVerilog top as a Vivado user IP (component.xml + xgui).
8
+ #
9
+ # Uses the built-in ipx:: packager (UG1118). Bus interfaces are inferred from
10
+ # (* X_INTERFACE_INFO = "..." *) attributes on the top module ports.
11
+ #
12
+ # Tclargs (fixed order, then one path per RTL file):
13
+ # <ip_name> <top_module> <ip_out_dir> <work_dir> <vendor> <library> <version> \
14
+ # <part> <ip_repo_paths> <source_0> [<source_1> ...]
15
+ #
16
+ # ip_repo_paths: semicolon-separated Vivado IP repository directories, or - for none
17
+ #
18
+ # Example:
19
+ # vivado -mode batch -notrace -source package_verilog_ip.tcl -tclargs \
20
+ # design design_top \
21
+ # design/ip \
22
+ # Projects/design user.org user 1.0 xck26-sfvc784-2LV-c \
23
+ # ip;src/ip \
24
+ # rtl/design_top.v ...
25
+
26
+ set nargs [llength $argv]
27
+ if {$nargs < 10} {
28
+ puts "Usage: vivado -mode batch -source package_verilog_ip.tcl -tclargs \\"
29
+ puts " <ip_name> <top_module> <ip_out_dir> <work_dir> \\"
30
+ puts " <vendor> <library> <version> <part> <ip_repo_paths> \\"
31
+ puts " <source_0> \[<source_1> ...\]"
32
+ exit 1
33
+ }
34
+
35
+ set ip_name [lindex $argv 0]
36
+ set top_module [lindex $argv 1]
37
+ set ip_out_dir [file normalize [lindex $argv 2]]
38
+ set work_dir [file normalize [lindex $argv 3]]
39
+ set ip_vendor [lindex $argv 4]
40
+ set ip_library [lindex $argv 5]
41
+ set ip_version [lindex $argv 6]
42
+ set part [lindex $argv 7]
43
+ set ip_repo_paths [lindex $argv 8]
44
+ set src_files [lrange $argv 9 end]
45
+
46
+ if {[llength $src_files] == 0} {
47
+ puts "ERROR: no source files"
48
+ exit 1
49
+ }
50
+
51
+ proc xgui_tcl_path {ip_root core_name core_version} {
52
+ set ver_suffix [string map {. _} $core_version]
53
+ return [file join $ip_root xgui ${core_name}_v${ver_suffix}.tcl]
54
+ }
55
+
56
+ proc remove_core_xgui_files {core} {
57
+ foreach group [ipx::get_file_groups -of_objects $core] {
58
+ if {![string match xilinx_xpgui* [get_property NAME $group]]} {
59
+ continue
60
+ }
61
+ foreach f [ipx::get_files -of_objects $group] {
62
+ ipx::remove_file $f $group
63
+ }
64
+ }
65
+ }
66
+
67
+ proc cleanup_stale_xgui_files {ip_root keep_path} {
68
+ set xgui_dir [file join $ip_root xgui]
69
+ if {![file isdirectory $xgui_dir]} {
70
+ return
71
+ }
72
+ set keep_norm [file normalize $keep_path]
73
+ foreach path [glob -nocomplain -directory $xgui_dir *.tcl] {
74
+ if {[file normalize $path] ne $keep_norm} {
75
+ file delete -force $path
76
+ }
77
+ }
78
+ }
79
+
80
+ puts "Packaging $top_module as $ip_name ([llength $src_files] files) -> $ip_out_dir"
81
+
82
+ file mkdir $work_dir
83
+ cd $work_dir
84
+
85
+ set proj_name ip_package_$ip_name
86
+ if {[file exists ${proj_name}.xpr]} {
87
+ file delete -force ${proj_name}.xpr ${proj_name}.cache ${proj_name}.runs ${proj_name}.srcs
88
+ }
89
+
90
+ create_project $proj_name . -part $part -force
91
+ set_property target_language Verilog [current_project]
92
+
93
+ if {$ip_repo_paths ne "-"} {
94
+ set repos {}
95
+ foreach r [split $ip_repo_paths ";"] {
96
+ if {$r ne ""} {
97
+ lappend repos [file normalize $r]
98
+ }
99
+ }
100
+ if {[llength $repos] > 0} {
101
+ set_property ip_repo_paths $repos [current_fileset]
102
+ puts "IP repo paths: $repos"
103
+ }
104
+ }
105
+
106
+ foreach f $src_files {
107
+ if {![file exists $f]} {
108
+ puts "ERROR: missing source $f"
109
+ exit 1
110
+ }
111
+ set ext [string tolower [file extension $f]]
112
+ if {$ext eq ".sv"} {
113
+ add_files -norecurse $f
114
+ set_property file_type SystemVerilog [get_files $f]
115
+ } else {
116
+ add_files -norecurse $f
117
+ }
118
+ }
119
+ update_compile_order -fileset sources_1
120
+ set_property top $top_module [current_fileset]
121
+
122
+ file mkdir $ip_out_dir
123
+ ipx::package_project -root_dir $ip_out_dir -vendor $ip_vendor -library $ip_library -taxonomy /UserIP
124
+
125
+ set core [ipx::current_core]
126
+ # package_project creates xgui/<top>_v<ver>.tcl before VLNV is customized.
127
+ set stale_xgui [xgui_tcl_path $ip_out_dir [get_property name $core] [get_property version $core]]
128
+
129
+ set_property name $ip_name $core
130
+ set_property version $ip_version $core
131
+ set_property display_name $ip_name $core
132
+ set_property description "Packaged IP $ip_name v$ip_version (top $top_module)" $core
133
+
134
+ catch {ipx::infer_bus_interface -all $core}
135
+
136
+ remove_core_xgui_files $core
137
+ if {[file exists $stale_xgui]} {
138
+ file delete -force $stale_xgui
139
+ }
140
+
141
+ ipx::create_xgui_files $core
142
+ set xgui_tcl [xgui_tcl_path $ip_out_dir $ip_name $ip_version]
143
+ cleanup_stale_xgui_files $ip_out_dir $xgui_tcl
144
+
145
+ ipx::update_checksums $core
146
+ ipx::save_core $core
147
+ ipx::unload_core $core
148
+
149
+ puts "Done: [file join $ip_out_dir component.xml]"
150
+ close_project
151
+ exit 0
@@ -0,0 +1,65 @@
1
+ # SPDX-FileCopyrightText: 2026 Maestro authors and contributors
2
+ # SPDX-FileContributor: Cedric Hirschi <cedr02@live.com>
3
+ # SPDX-FileContributor: Tim Frey <timfreyb@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ # TCL script to program an FPGA using Vivado Hardware Manager
8
+ # Arguments:
9
+ # 1. bitfile path
10
+ # 2. device name (optional, default: xck26_0)
11
+
12
+ # Get the bitfile from command line arguments
13
+ puts $argv
14
+ set file [lindex $argv 0]
15
+
16
+ # Get the device name from command line arguments
17
+ set device [lindex $argv 1]
18
+ if {![info exists device]} {
19
+ set device {xck26_0}
20
+ }
21
+
22
+ open_hw_manager
23
+ connect_hw_server
24
+ open_hw_target
25
+ puts [get_hw_devices]
26
+
27
+ # Connect to device
28
+ puts "Connecting to device $device"
29
+
30
+ current_hw_device [get_hw_devices $device]
31
+ refresh_hw_device -update_hw_probes false [lindex [get_hw_devices $device] 0]
32
+
33
+
34
+ # check if file exists
35
+ if {![file exists $file]} {
36
+ puts "Error: File $file does not exist."
37
+
38
+ set file bin/${file}/${file}.bit
39
+ if {![file exists $file]} {
40
+ puts "Error: File $file does not exist."
41
+ exit 1
42
+ } else {
43
+ puts "Using alternative file path: $file"
44
+ }
45
+ }
46
+
47
+ # .ltx file ??
48
+ set ltx_file [file rootname $file].ltx
49
+ if {[file exists $ltx_file]} {
50
+ puts "Found LTX file: $ltx_file"
51
+ set_property PROBES.FILE $ltx_file [get_hw_devices $device]
52
+ } else {
53
+ puts "No LTX file found for $file"
54
+ set_property PROBES.FILE {} [get_hw_devices $device]
55
+ }
56
+
57
+ set_property PROGRAM.FILE $file [get_hw_devices $device]
58
+
59
+ program_hw_devices [get_hw_devices $device]
60
+ refresh_hw_device [lindex [get_hw_devices $device] 0]
61
+ close_hw_target
62
+
63
+ puts ""
64
+ puts "Done programming $file"
65
+ puts " to $device"
@@ -0,0 +1,27 @@
1
+ # SPDX-FileCopyrightText: 2026 Maestro authors and contributors
2
+ # SPDX-FileContributor: Cedric Hirschi <cedr02@live.com>
3
+ # SPDX-FileContributor: Tim Frey <timfreyb@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ # Default verible-verilog-format flags (one flag per line).
8
+ --assignment_statement_alignment=align
9
+ --case_items_alignment=align
10
+ --class_member_variable_alignment=align
11
+ --compact_indexing_and_selections=true
12
+ --distribution_items_alignment=align
13
+ --enum_assignment_statement_alignment=align
14
+ --expand_coverpoints=true
15
+ --formal_parameters_alignment=align
16
+ --formal_parameters_indentation=indent
17
+ --module_net_variable_alignment=align
18
+ --named_parameter_alignment=align
19
+ --named_parameter_indentation=indent
20
+ --named_port_alignment=align
21
+ --named_port_indentation=indent
22
+ --port_declarations_alignment=align
23
+ --port_declarations_indentation=indent
24
+ --port_declarations_right_align_packed_dimensions=true
25
+ --port_declarations_right_align_unpacked_dimensions=true
26
+ --struct_union_members_alignment=align
27
+ --try_wrap_long_lines=false
@@ -0,0 +1,8 @@
1
+ # SPDX-FileCopyrightText: 2026 Maestro authors and contributors
2
+ # SPDX-FileContributor: Cedric Hirschi <cedr02@live.com>
3
+ # SPDX-FileContributor: Tim Frey <timfreyb@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ # Allow localparam/parameter names like TEST_WIDTH (SCREAMING_SNAKE_CASE).
8
+ parameter-name-style=localparam_style:ALL_CAPS;parameter_style:ALL_CAPS
@@ -0,0 +1,90 @@
1
+ # SPDX-FileCopyrightText: 2026 Maestro authors and contributors
2
+ # SPDX-FileContributor: Cedric Hirschi <cedr02@live.com>
3
+ # SPDX-FileContributor: Tim Frey <timfreyb@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ import os
8
+ from pathlib import Path
9
+
10
+ import vitis
11
+ from rpu_create import (
12
+ COMPILE_SOURCE_SUFFIXES,
13
+ link_app_sources,
14
+ update_user_compile_sources,
15
+ )
16
+
17
+
18
+ def get_app_sources(root_dir: Path) -> list[Path]:
19
+ app_sources = os.environ.get("RPU_APP_SOURCES")
20
+ if not app_sources:
21
+ print("Error: RPU_APP_SOURCES environment variable not set.")
22
+ exit(1)
23
+
24
+ app_source_patterns = [src.strip() for src in app_sources.split(":") if src.strip()]
25
+ if not app_source_patterns:
26
+ print("Error: RPU_APP_SOURCES environment variable is empty.")
27
+ exit(1)
28
+
29
+ resolved_sources = []
30
+ for src_pattern in app_source_patterns:
31
+ matched_files = [path for path in root_dir.glob(src_pattern) if path.is_file()]
32
+ if not matched_files:
33
+ print(
34
+ f"Error: No files matched the pattern {src_pattern!r} in RPU_APP_SOURCES."
35
+ )
36
+ exit(1)
37
+ resolved_sources.extend(matched_files)
38
+ print(f"- Pattern {src_pattern!r} matched {len(matched_files)} files.")
39
+
40
+ return resolved_sources
41
+
42
+
43
+ def main():
44
+ app_name = os.environ.get("RPU_APP_NAME")
45
+ if not app_name:
46
+ print("Error: RPU_APP_NAME environment variable not set.")
47
+ exit(1)
48
+
49
+ root_dir = os.environ.get("RPU_ROOT_DIR")
50
+ if not root_dir:
51
+ print("Error: RPU_ROOT_DIR environment variable not set.")
52
+ exit(1)
53
+ root_dir = Path(root_dir).resolve()
54
+ print(f"Using root directory: {root_dir.as_posix()!r}")
55
+
56
+ print("Creating Vitis client...")
57
+ client = vitis.create_client()
58
+
59
+ workspace = root_dir / "workspace"
60
+ if not workspace.is_dir():
61
+ print(f"Error: Workspace '{workspace}' does not exist. Run setup first.")
62
+ exit(1)
63
+
64
+ client.set_workspace(workspace)
65
+
66
+ print("Getting app...")
67
+ app = client.get_component(name=app_name)
68
+ if not app:
69
+ print(f"Error: App '{app_name}' does not exist. Create the app first.")
70
+ exit(1)
71
+
72
+ print("Syncing app sources...")
73
+ app_sources = get_app_sources(root_dir)
74
+ linked_sources = link_app_sources(app.component_location, app_sources)
75
+ compile_sources = [
76
+ source
77
+ for source in linked_sources
78
+ if Path(source).suffix in COMPILE_SOURCE_SUFFIXES
79
+ ]
80
+ if compile_sources:
81
+ user_config_path = Path(app.component_location) / "src" / "UserConfig.cmake"
82
+ update_user_compile_sources(user_config_path, compile_sources)
83
+
84
+ print("Cleaning and building app...")
85
+ app.clean()
86
+ app.build(target="hw")
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()