diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 92c45448acef..ff70d08239de 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -325,7 +325,7 @@ }, "//build:jps_dynamic_deps_community.bzl%jps_dynamic_deps_community_extension": { "general": { - "bzlTransitiveDigest": "3xhuyu/RwZY46XM2F9PzxVuqXwuOnK9XPrCPpt/gKTI=", + "bzlTransitiveDigest": "3CitlyO8eztna/EextdwgGkMSx42lDg2RITKuKTLH0U=", "usagesDigest": "OSsQY2hkpxdAkgfVzTh+Q1KLtzAuQDVI0/8sRki7FfU=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, diff --git a/build/jps_dynamic_deps_community.bzl b/build/jps_dynamic_deps_community.bzl index 5ab261221107..e537a9385ab8 100644 --- a/build/jps_dynamic_deps_community.bzl +++ b/build/jps_dynamic_deps_community.bzl @@ -36,6 +36,8 @@ STALENESS & WATCHING: This repository is invalidated on changes to `.idea/modules.xml` via ctx.watch(). """ +load(":jps_model.bzl", "watch_project_model_files") + def _format_target_list(name, targets): """Format a list of targets as a Starlark list assignment.""" if not targets: @@ -60,11 +62,8 @@ def _targets_repo_impl(ctx): # SETUP CONTEXT AND WATCHER root = ctx.path(Label("@community//:MODULE.bazel")).dirname - # we set watcher on .idea/modules.xml, because we only need to regenerate targets' list if a new module was added - # otherwise the list get populated on import and bazel clean invalidates it anyway - idea_dir = root.get_child(".idea") - modules_xml = idea_dir.get_child("modules.xml") - ctx.watch(modules_xml) + # Watch the entire model to re-run generator on changes + watch_project_model_files(ctx, root) script = ( root diff --git a/build/jps_model.bzl b/build/jps_model.bzl new file mode 100644 index 000000000000..d1fdc2f188b0 --- /dev/null +++ b/build/jps_model.bzl @@ -0,0 +1,42 @@ +def _parse_project_relative_module_paths_strict(modules_xml_text): + marker = 'filepath="' + prefix = "$PROJECT_DIR$/" + + rels = [] + i = 0 + + # Upper bound: you can't have more attributes than characters. + for _ in range(len(modules_xml_text) + 1): + j = modules_xml_text.find(marker, i) + if j == -1: + break + + j += len(marker) + k = modules_xml_text.find('"', j) + if k == -1: + fail("Malformed XML: unterminated filepath attribute (missing closing quote)") + + full = modules_xml_text[j:k] + if not full.startswith(prefix): + fail("Unexpected module filepath (expected to start with %r): %r" % (prefix, full)) + + rels.append(full[len(prefix):]) + i = k + 1 + + if not rels: + fail("No module filepaths found") + + return rels + +def watch_project_model_files(ctx, project_root): + idea_dir = project_root.get_child(".idea") + modules_xml = idea_dir.get_child("modules.xml") + + # Read (to make sure it exists) and watch all modules to re-run generator on changes + for relativeModulePath in _parse_project_relative_module_paths_strict(ctx.read(modules_xml, watch='yes')): + ctx.read(project_root.get_child(relativeModulePath), watch='yes') + + # Read (to make sure it exists) and watch all libraries under .idea/libraries to re-run generator on changes + libraries_dir = idea_dir.get_child("libraries") + for library_xml in libraries_dir.readdir(watch='yes'): + ctx.read(library_xml, watch='yes')