mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
Support extracted QML layout and resource aliases
Add support for an extracted component layout where QML files live in ../../qml/ and may need QT_RESOURCE_ALIAS to preserve original QRC URIs. Refactor scanning logic (_scan_dir), make find_root_qml_files and find_qmllib_files return (path, alias) tuples, add find_config_files, and collect aliased resources to set QT_RESOURCE_ALIAS properties before qt_add_qml_module. Also streamline source/resource aggregation, compile-def handling, and generation header/output reporting.
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
Scans QML files, C++ sources, SVG/audio assets, and package metadata to produce
|
||||
a complete CMakeLists.txt for the Qt6 DBAL Observatory frontend.
|
||||
|
||||
Supports extracted component layout where QML files live in ../../qml/ and are
|
||||
referenced via relative paths with QT_RESOURCE_ALIAS for correct QRC URIs.
|
||||
|
||||
Usage:
|
||||
python3 generate_cmake.py # Write CMakeLists.txt
|
||||
python3 generate_cmake.py --dry-run # Print without writing
|
||||
@@ -12,11 +15,11 @@ Usage:
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def load_config(config_path: str) -> dict:
|
||||
@@ -29,64 +32,83 @@ def load_config(config_path: str) -> dict:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def find_root_qml_files(root_dir: Path) -> list[tuple[str, str]]:
|
||||
"""Find all *.qml and *.js files in the project root and extracted qml/qt6/ directory.
|
||||
def _scan_dir(directory: str, extensions: tuple[str, ...]) -> list[str]:
|
||||
"""Walk a directory (following symlinks) and return matching files."""
|
||||
result = []
|
||||
for dirpath, _dirnames, filenames in os.walk(directory, followlinks=True):
|
||||
for fn in filenames:
|
||||
if fn.endswith(extensions):
|
||||
result.append(os.path.join(dirpath, fn))
|
||||
return sorted(result)
|
||||
|
||||
Returns list of (source_path, qrc_alias) tuples. Files in root_dir use just their
|
||||
filename; files in ../../qml/qt6/ use relative paths with a QT_RESOURCE_ALIAS.
|
||||
|
||||
def find_root_qml_files(root_dir: Path) -> list[tuple[str, Optional[str]]]:
|
||||
"""Find root QML/JS files. Returns (rel_path, alias_or_None) tuples.
|
||||
|
||||
Checks both the local directory and ../../qml/qt6/ for extracted files.
|
||||
Extracted files get a QT_RESOURCE_ALIAS so their QRC path matches the original.
|
||||
"""
|
||||
result = []
|
||||
# Local root files
|
||||
# Local files
|
||||
for fn in sorted(os.listdir(str(root_dir))):
|
||||
if fn.endswith((".qml", ".js")):
|
||||
if fn.endswith((".qml", ".js")) and os.path.isfile(root_dir / fn):
|
||||
result.append((fn, None))
|
||||
|
||||
# Extracted files in ../../qml/qt6/
|
||||
local_names = {t[0] for t in result}
|
||||
extracted_dir = root_dir.parent.parent / "qml" / "qt6"
|
||||
if extracted_dir.exists():
|
||||
local_names = {t[0] for t in result}
|
||||
for fn in sorted(os.listdir(str(extracted_dir))):
|
||||
if fn.endswith((".qml", ".js")) and fn not in local_names:
|
||||
rel_path = os.path.relpath(extracted_dir / fn, root_dir)
|
||||
result.append((rel_path, fn))
|
||||
rel = os.path.relpath(extracted_dir / fn, root_dir)
|
||||
result.append((rel, fn))
|
||||
return result
|
||||
|
||||
|
||||
def find_qmllib_files(root_dir: Path) -> dict[str, list[str]]:
|
||||
"""Find all *.qml and *.js files and qmldir files in qmllib/ subdirectories.
|
||||
def find_qmllib_files(root_dir: Path) -> dict[str, list[tuple[str, Optional[str]]]]:
|
||||
"""Find qmllib QML/JS and qmldir files. Returns dict with 'qml' and 'resources'.
|
||||
|
||||
Follows symlinks so that extracted component directories (e.g., qmllib/MetaBuilder
|
||||
symlinked to ../../qml/MetaBuilder) are included with qmllib-relative paths.
|
||||
|
||||
Returns a dict with 'qml' (list of QML/JS paths) and 'resources' (qmldir paths).
|
||||
Searches local qmllib/ (following symlinks) and extracted ../../qml/{module}/
|
||||
directories. Extracted files get QT_RESOURCE_ALIAS for correct QRC URIs.
|
||||
"""
|
||||
result = {"qml": [], "resources": []}
|
||||
result: dict[str, list[tuple[str, Optional[str]]]] = {"qml": [], "resources": []}
|
||||
|
||||
# Search both local qmllib/ and extracted ../../qml/{MetaBuilder,Material,dbal}
|
||||
search_dirs = []
|
||||
# Mapping of (real_directory, qrc_prefix)
|
||||
dirs_to_scan: list[tuple[Path, str]] = []
|
||||
|
||||
# Local qmllib/ with symlinks
|
||||
qmllib_dir = root_dir / "qmllib"
|
||||
if qmllib_dir.exists():
|
||||
search_dirs.append((qmllib_dir, "qmllib"))
|
||||
dirs_to_scan.append((qmllib_dir, "qmllib"))
|
||||
|
||||
# Extracted ../../qml/{MetaBuilder,Material,dbal}
|
||||
extracted_qml = root_dir.parent.parent / "qml"
|
||||
if extracted_qml.exists():
|
||||
for subdir in ["MetaBuilder", "Material", "dbal"]:
|
||||
candidate = extracted_qml / subdir
|
||||
if candidate.exists():
|
||||
search_dirs.append((candidate, f"qmllib/{subdir}"))
|
||||
for module in ["MetaBuilder", "Material", "dbal"]:
|
||||
candidate = extracted_qml / module
|
||||
if candidate.exists() and not (qmllib_dir / module).exists():
|
||||
dirs_to_scan.append((candidate, f"qmllib/{module}"))
|
||||
|
||||
for search_dir, prefix in search_dirs:
|
||||
for dirpath, _dirnames, filenames in os.walk(str(search_dir), followlinks=True):
|
||||
for scan_dir, prefix in dirs_to_scan:
|
||||
for dirpath, _dirnames, filenames in os.walk(str(scan_dir), followlinks=True):
|
||||
for fn in filenames:
|
||||
full = os.path.join(dirpath, fn)
|
||||
# Compute path relative to search_dir, then prepend prefix
|
||||
rel_to_search = os.path.relpath(full, str(search_dir))
|
||||
aliased = f"{prefix}/{rel_to_search}" if prefix == f"qmllib/{search_dir.name}" else os.path.relpath(full, str(root_dir))
|
||||
real_path = os.path.relpath(full, str(root_dir))
|
||||
rel_to_root = os.path.relpath(full, str(root_dir))
|
||||
# Compute the alias: prefix + path relative to scan_dir
|
||||
rel_to_scan = os.path.relpath(full, str(scan_dir))
|
||||
alias_path = f"{prefix}/{rel_to_scan}"
|
||||
|
||||
# Only need alias if real path differs from desired QRC path
|
||||
needs_alias = rel_to_root != alias_path
|
||||
|
||||
if fn.endswith((".qml", ".js")):
|
||||
result["qml"].append((real_path, f"{prefix}/{rel_to_search}"))
|
||||
result["qml"].append((rel_to_root, alias_path if needs_alias else None))
|
||||
elif fn == "qmldir":
|
||||
result["resources"].append((real_path, f"{prefix}/{rel_to_search}"))
|
||||
result["resources"].append((rel_to_root, alias_path if needs_alias else None))
|
||||
|
||||
# Sort by alias (or real path)
|
||||
result["qml"].sort(key=lambda t: t[1] or t[0])
|
||||
result["resources"].sort(key=lambda t: t[1] or t[0])
|
||||
return result
|
||||
|
||||
|
||||
@@ -101,6 +123,19 @@ def find_package_qml_files(root_dir: Path) -> list[str]:
|
||||
return [str(f.relative_to(root_dir)) for f in files]
|
||||
|
||||
|
||||
def find_config_files(root_dir: Path) -> dict[str, list[str]]:
|
||||
"""Find config/ files: JS goes into QML_FILES, JSON into RESOURCES."""
|
||||
config_dir = root_dir / "config"
|
||||
result = {"qml": [], "resources": []}
|
||||
if not config_dir.exists():
|
||||
return result
|
||||
for f in sorted(config_dir.rglob("*.js")):
|
||||
result["qml"].append(str(f.relative_to(root_dir)))
|
||||
for f in sorted(config_dir.rglob("*.json")):
|
||||
result["resources"].append(str(f.relative_to(root_dir)))
|
||||
return result
|
||||
|
||||
|
||||
def load_package_metadata(root_dir: Path) -> list[dict]:
|
||||
"""Read metadata.json from each packages/ subdirectory."""
|
||||
packages_dir = root_dir / "packages"
|
||||
@@ -133,19 +168,6 @@ def find_audio_assets(root_dir: Path) -> list[str]:
|
||||
return [str(f.relative_to(root_dir)) for f in files if f.is_file()]
|
||||
|
||||
|
||||
def find_config_files(root_dir: Path) -> dict[str, list[str]]:
|
||||
"""Find config/ files: JS goes into QML_FILES, JSON into RESOURCES."""
|
||||
config_dir = root_dir / "config"
|
||||
result = {"qml": [], "resources": []}
|
||||
if not config_dir.exists():
|
||||
return result
|
||||
for f in sorted(config_dir.rglob("*.js")):
|
||||
result["qml"].append(str(f.relative_to(root_dir)))
|
||||
for f in sorted(config_dir.rglob("*.json")):
|
||||
result["resources"].append(str(f.relative_to(root_dir)))
|
||||
return result
|
||||
|
||||
|
||||
def find_cpp_sources(root_dir: Path) -> dict[str, list[str]]:
|
||||
"""Find all *.cpp and *.h files in src/."""
|
||||
src_dir = root_dir / "src"
|
||||
@@ -161,12 +183,6 @@ def find_cpp_sources(root_dir: Path) -> dict[str, list[str]]:
|
||||
return result
|
||||
|
||||
|
||||
def indent_list(items: list[str], spaces: int = 8) -> str:
|
||||
"""Format a list of file paths as indented CMake entries."""
|
||||
prefix = " " * spaces
|
||||
return "\n".join(f"{prefix}{item}" for item in items)
|
||||
|
||||
|
||||
def generate_cmake(config: dict, root_dir: Path) -> str:
|
||||
"""Generate the full CMakeLists.txt content from config and discovered files."""
|
||||
proj = config["project"]
|
||||
@@ -187,35 +203,44 @@ def generate_cmake(config: dict, root_dir: Path) -> str:
|
||||
packages_meta = load_package_metadata(root_dir)
|
||||
|
||||
# Build Qt components string
|
||||
qt_components = " ".join(qt["components"])
|
||||
|
||||
# Conditional components from features
|
||||
extra_components = []
|
||||
if features.get("qt_multimedia"):
|
||||
extra_components.append("Multimedia")
|
||||
|
||||
all_components = qt["components"] + extra_components
|
||||
qt_components_str = " ".join(all_components)
|
||||
|
||||
# Build source files list (main.cpp + src/*.cpp)
|
||||
source_files = ["main.cpp"]
|
||||
source_files.extend(cpp_sources["cpp"])
|
||||
# Build source files list
|
||||
source_files = ["main.cpp"] + cpp_sources["cpp"]
|
||||
|
||||
# Build QML files list: root QML + qmllib QML + package QML + config JS
|
||||
all_qml_files = root_qml + qmllib["qml"] + package_qml + config_files["qml"]
|
||||
# Collect all QML files: (path, alias_or_None)
|
||||
all_qml: list[tuple[str, Optional[str]]] = []
|
||||
all_qml.extend(root_qml)
|
||||
all_qml.extend(qmllib["qml"])
|
||||
all_qml.extend((p, None) for p in package_qml)
|
||||
all_qml.extend((p, None) for p in config_files["qml"])
|
||||
|
||||
# Build RESOURCES list: audio + config JSON + qmllib resources (qmldir files)
|
||||
resource_files = audio_assets + config_files["resources"] + qmllib["resources"]
|
||||
# Collect all resource files: (path, alias_or_None)
|
||||
all_res: list[tuple[str, Optional[str]]] = []
|
||||
all_res.extend((p, None) for p in audio_assets)
|
||||
all_res.extend((p, None) for p in config_files["resources"])
|
||||
all_res.extend(qmllib["resources"])
|
||||
|
||||
# Build compile definitions
|
||||
defs_lines = []
|
||||
for key, value in compile_defs.items():
|
||||
defs_lines.append(f'target_compile_definitions({proj["executable"]} PRIVATE {key}="{value}")')
|
||||
# Separate files needing aliases
|
||||
aliased_files = [(path, alias) for path, alias in all_qml + all_res if alias]
|
||||
|
||||
# Build link libraries
|
||||
# Total counts for header
|
||||
total_qml = len(all_qml)
|
||||
|
||||
# Compile definitions
|
||||
defs_lines = [
|
||||
f'target_compile_definitions({proj["executable"]} PRIVATE {k}="{v}")'
|
||||
for k, v in compile_defs.items()
|
||||
]
|
||||
|
||||
# Link libraries
|
||||
link_libs = " ".join(f"Qt6::{c}" for c in all_components)
|
||||
|
||||
# Optional feature blocks
|
||||
# Feature blocks
|
||||
feature_blocks = []
|
||||
if features.get("libopenmpt"):
|
||||
feature_blocks.append(f"""
|
||||
@@ -235,10 +260,10 @@ target_link_libraries({proj["executable"]} PRIVATE ${{OPENMPT_LIBRARIES}})""")
|
||||
f'v{meta.get("version", "?")} - {meta.get("name", "")}'
|
||||
)
|
||||
|
||||
# Assemble the CMakeLists.txt
|
||||
# ── Assemble CMakeLists.txt ───────────────────────────────────────
|
||||
lines = []
|
||||
lines.append("# AUTO-GENERATED by generate_cmake.py — do not edit manually")
|
||||
lines.append(f"# Generated from cmake_config.json | {len(all_qml_files)} QML files, "
|
||||
lines.append(f"# Generated from cmake_config.json | {total_qml} QML files, "
|
||||
f"{len(source_files)} C++ sources, {len(svg_assets)} SVGs, "
|
||||
f"{len(audio_assets)} audio assets")
|
||||
if pkg_comment_lines:
|
||||
@@ -277,21 +302,28 @@ target_link_libraries({proj["executable"]} PRIVATE ${{OPENMPT_LIBRARIES}})""")
|
||||
if defs_lines:
|
||||
lines.append("")
|
||||
|
||||
# Set QT_RESOURCE_ALIAS for external files (before qt_add_qml_module)
|
||||
if aliased_files:
|
||||
lines.append("# Map extracted files to their original QRC paths")
|
||||
for path, alias in aliased_files:
|
||||
lines.append(f'set_source_files_properties({path} PROPERTIES QT_RESOURCE_ALIAS {alias})')
|
||||
lines.append("")
|
||||
|
||||
# qt_add_qml_module
|
||||
lines.append(f"qt_add_qml_module({proj['executable']}")
|
||||
lines.append(f" URI {qml['uri']}")
|
||||
lines.append(f" VERSION {qml['version']}")
|
||||
lines.append(" QML_FILES")
|
||||
for qf in all_qml_files:
|
||||
lines.append(f" {qf}")
|
||||
if resource_files:
|
||||
for path, _alias in all_qml:
|
||||
lines.append(f" {path}")
|
||||
if all_res:
|
||||
lines.append(" RESOURCES")
|
||||
for rf in resource_files:
|
||||
lines.append(f" {rf}")
|
||||
for path, _alias in all_res:
|
||||
lines.append(f" {path}")
|
||||
lines.append(")")
|
||||
lines.append("")
|
||||
|
||||
# SVG assets via file(GLOB) + qt_add_resources
|
||||
# SVG assets
|
||||
if svg_assets:
|
||||
lines.append("# SVG assets")
|
||||
lines.append(f"file(GLOB SVG_ASSETS RELATIVE ${{CMAKE_CURRENT_SOURCE_DIR}} assets/svg/*.svg)")
|
||||
@@ -379,7 +411,7 @@ def main():
|
||||
else:
|
||||
root_dir = Path(__file__).parent.resolve()
|
||||
|
||||
# Resolve config path relative to root if not absolute
|
||||
# Resolve config path
|
||||
config_path = Path(args.config)
|
||||
if not config_path.is_absolute():
|
||||
config_path = root_dir / config_path
|
||||
@@ -391,7 +423,7 @@ def main():
|
||||
print(content)
|
||||
return
|
||||
|
||||
# Resolve output path relative to root if not absolute
|
||||
# Resolve output path
|
||||
output_path = Path(args.output)
|
||||
if not output_path.is_absolute():
|
||||
output_path = root_dir / output_path
|
||||
@@ -409,7 +441,7 @@ def main():
|
||||
packages_meta = load_package_metadata(root_dir)
|
||||
|
||||
total_qml = len(root_qml) + len(qmllib["qml"]) + len(package_qml)
|
||||
total_cpp = len(cpp_sources["cpp"]) + 1 # +1 for main.cpp
|
||||
total_cpp = len(cpp_sources["cpp"]) + 1
|
||||
|
||||
print(f"Generated {output_path}")
|
||||
print(f" QML files: {total_qml} ({len(root_qml)} root, {len(qmllib['qml'])} qmllib, {len(package_qml)} packages)")
|
||||
@@ -418,6 +450,10 @@ def main():
|
||||
print(f" SVG assets: {len(svg_assets)}")
|
||||
print(f" Audio assets: {len(audio_assets)}")
|
||||
print(f" Packages: {len(packages_meta)} with metadata.json")
|
||||
aliased = len(root_qml) + len(qmllib["qml"]) + len(qmllib["resources"])
|
||||
aliased_count = sum(1 for _, a in root_qml if a) + sum(1 for _, a in qmllib["qml"] if a) + sum(1 for _, a in qmllib["resources"] if a)
|
||||
if aliased_count:
|
||||
print(f" Aliased: {aliased_count} files with QT_RESOURCE_ALIAS (extracted layout)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user