|
| 1 | +Import('env') |
| 2 | +import os.path |
| 3 | +from collections import deque |
| 4 | +from pathlib import Path # For OS-agnostic path manipulation |
| 5 | +from platformio.package.manager.library import LibraryPackageManager |
| 6 | + |
| 7 | +usermod_dir = Path(env["PROJECT_DIR"]) / "usermods" |
| 8 | +all_usermods = [f for f in usermod_dir.iterdir() if f.is_dir() and f.joinpath('library.json').exists()] |
| 9 | + |
| 10 | +if env['PIOENV'] == "usermods": |
| 11 | + # Add all usermods |
| 12 | + env.GetProjectConfig().set(f"env:usermods", 'custom_usermods', " ".join([f.name for f in all_usermods])) |
| 13 | + |
| 14 | +def find_usermod(mod: str): |
| 15 | + """Locate this library in the usermods folder. |
| 16 | + We do this to avoid needing to rename a bunch of folders; |
| 17 | + this could be removed later |
| 18 | + """ |
| 19 | + # Check name match |
| 20 | + mp = usermod_dir / mod |
| 21 | + if mp.exists(): |
| 22 | + return mp |
| 23 | + mp = usermod_dir / f"{mod}_v2" |
| 24 | + if mp.exists(): |
| 25 | + return mp |
| 26 | + mp = usermod_dir / f"usermod_v2_{mod}" |
| 27 | + if mp.exists(): |
| 28 | + return mp |
| 29 | + raise RuntimeError(f"Couldn't locate module {mod} in usermods directory!") |
| 30 | + |
| 31 | +usermods = env.GetProjectOption("custom_usermods","") |
| 32 | +if usermods: |
| 33 | + # Inject usermods in to project lib_deps |
| 34 | + proj = env.GetProjectConfig() |
| 35 | + deps = env.GetProjectOption('lib_deps') |
| 36 | + src_dir = proj.get("platformio", "src_dir") |
| 37 | + src_dir = src_dir.replace('\\','/') |
| 38 | + mod_paths = {mod: find_usermod(mod) for mod in usermods.split()} |
| 39 | + usermods = [f"{mod} = symlink://{path}" for mod, path in mod_paths.items()] |
| 40 | + proj.set("env:" + env['PIOENV'], 'lib_deps', deps + usermods) |
| 41 | + # Force usermods to be installed in to the environment build state before the LDF runs |
| 42 | + # Otherwise we won't be able to see them until it's too late to change their paths for LDF |
| 43 | + # Logic is largely borrowed from PlaformIO internals |
| 44 | + not_found_specs = [] |
| 45 | + for spec in usermods: |
| 46 | + found = False |
| 47 | + for storage_dir in env.GetLibSourceDirs(): |
| 48 | + #print(f"Checking {storage_dir} for {spec}") |
| 49 | + lm = LibraryPackageManager(storage_dir) |
| 50 | + if lm.get_package(spec): |
| 51 | + #print("Found!") |
| 52 | + found = True |
| 53 | + break |
| 54 | + if not found: |
| 55 | + #print("Missing!") |
| 56 | + not_found_specs.append(spec) |
| 57 | + if not_found_specs: |
| 58 | + lm = LibraryPackageManager( |
| 59 | + env.subst(os.path.join("$PROJECT_LIBDEPS_DIR", "$PIOENV")) |
| 60 | + ) |
| 61 | + for spec in not_found_specs: |
| 62 | + #print(f"LU: forcing install of {spec}") |
| 63 | + lm.install(spec) |
| 64 | + |
| 65 | + |
| 66 | +# Utility function for assembling usermod include paths |
| 67 | +def cached_add_includes(dep, dep_cache: set, includes: deque): |
| 68 | + """ Add dep's include paths to includes if it's not in the cache """ |
| 69 | + if dep not in dep_cache: |
| 70 | + dep_cache.add(dep) |
| 71 | + for include in dep.get_include_dirs(): |
| 72 | + if include not in includes: |
| 73 | + includes.appendleft(include) |
| 74 | + if usermod_dir not in Path(dep.src_dir).parents: |
| 75 | + # Recurse, but only for NON-usermods |
| 76 | + for subdep in dep.depbuilders: |
| 77 | + cached_add_includes(subdep, dep_cache, includes) |
| 78 | + |
| 79 | +# Monkey-patch ConfigureProjectLibBuilder to mark up the dependencies |
| 80 | +# Save the old value |
| 81 | +old_ConfigureProjectLibBuilder = env.ConfigureProjectLibBuilder |
| 82 | + |
| 83 | +# Our new wrapper |
| 84 | +def wrapped_ConfigureProjectLibBuilder(xenv): |
| 85 | + # Update usermod properties |
| 86 | + # Set libArchive before build actions are added |
| 87 | + for um in (um for um in xenv.GetLibBuilders() if usermod_dir in Path(um.src_dir).parents): |
| 88 | + build = um._manifest.get("build", {}) |
| 89 | + build["libArchive"] = False |
| 90 | + um._manifest["build"] = build |
| 91 | + |
| 92 | + # Call the wrapped function |
| 93 | + result = old_ConfigureProjectLibBuilder.clone(xenv)() |
| 94 | + |
| 95 | + # Fix up include paths |
| 96 | + # In PlatformIO >=6.1.17, this could be done prior to ConfigureProjectLibBuilder |
| 97 | + wled_dir = xenv["PROJECT_SRC_DIR"] |
| 98 | + # Build a list of dependency include dirs |
| 99 | + # TODO: Find out if this is the order that PlatformIO/SCons puts them in?? |
| 100 | + processed_deps = set() |
| 101 | + extra_include_dirs = deque() # Deque used for fast prepend |
| 102 | + for dep in result.depbuilders: |
| 103 | + cached_add_includes(dep, processed_deps, extra_include_dirs) |
| 104 | + |
| 105 | + for um in [dep for dep in result.depbuilders if usermod_dir in Path(dep.src_dir).parents]: |
| 106 | + # Add the wled folder to the include path |
| 107 | + um.env.PrependUnique(CPPPATH=wled_dir) |
| 108 | + # Add WLED's own dependencies |
| 109 | + for dir in extra_include_dirs: |
| 110 | + um.env.PrependUnique(CPPPATH=dir) |
| 111 | + |
| 112 | + return result |
| 113 | + |
| 114 | +# Apply the wrapper |
| 115 | +env.AddMethod(wrapped_ConfigureProjectLibBuilder, "ConfigureProjectLibBuilder") |
0 commit comments