Fix check for outdated Rust library (#17861)
This failed when install with poetry, so let's properly try and detect
what's going on.
2 files changed, 66 insertions, 22 deletions
diff --git a/changelog.d/17861.bugfix b/changelog.d/17861.bugfix
new file mode 100644
index 0000000000..abee7a30f7
--- /dev/null
+++ b/changelog.d/17861.bugfix
@@ -0,0 +1 @@
+Fix detection when the built Rust library was outdated when using source installations.
diff --git a/synapse/util/rust.py b/synapse/util/rust.py
index 0e35d6d188..37f43459f1 100644
--- a/synapse/util/rust.py
+++ b/synapse/util/rust.py
@@ -19,9 +19,12 @@
#
#
+import json
import os
-import sys
+import urllib.parse
from hashlib import blake2b
+from importlib.metadata import Distribution, PackageNotFoundError
+from typing import Optional
import synapse
from synapse.synapse_rust import get_rust_file_digest
@@ -32,22 +35,17 @@ def check_rust_lib_up_to_date() -> None:
be rebuilt.
"""
- if not _dist_is_editable():
- return
-
- synapse_dir = os.path.dirname(synapse.__file__)
- synapse_root = os.path.abspath(os.path.join(synapse_dir, ".."))
-
- # Double check we've not gone into site-packages...
- if os.path.basename(synapse_root) == "site-packages":
- return
-
- # ... and it looks like the root of a python project.
- if not os.path.exists("pyproject.toml"):
- return
+ # Get the location of the editable install.
+ synapse_root = get_synapse_source_directory()
+ if synapse_root is None:
+ return None
# Get the hash of all Rust source files
- hash = _hash_rust_files_in_directory(os.path.join(synapse_root, "rust", "src"))
+ rust_path = os.path.join(synapse_root, "rust", "src")
+ if not os.path.exists(rust_path):
+ return None
+
+ hash = _hash_rust_files_in_directory(rust_path)
if hash != get_rust_file_digest():
raise Exception("Rust module outdated. Please rebuild using `poetry install`")
@@ -82,10 +80,55 @@ def _hash_rust_files_in_directory(directory: str) -> str:
return hasher.hexdigest()
-def _dist_is_editable() -> bool:
- """Is distribution an editable install?"""
- for path_item in sys.path:
- egg_link = os.path.join(path_item, "matrix-synapse.egg-link")
- if os.path.isfile(egg_link):
- return True
- return False
+def get_synapse_source_directory() -> Optional[str]:
+ """Try and find the source directory of synapse for editable installs (like
+ those used in development).
+
+ Returns None if not an editable install (or otherwise can't find the source
+ directory).
+ """
+
+ # Try and find the installed matrix-synapse package.
+ try:
+ package = Distribution.from_name("matrix-synapse")
+ except PackageNotFoundError:
+ # The package is not found, so it's not installed and so must be being
+ # pulled out from a local directory (usually the current one).
+ synapse_dir = os.path.dirname(synapse.__file__)
+ synapse_root = os.path.abspath(os.path.join(synapse_dir, ".."))
+
+ # Double check we've not gone into site-packages...
+ if os.path.basename(synapse_root) == "site-packages":
+ return None
+
+ # ... and it looks like the root of a python project.
+ if not os.path.exists("pyproject.toml"):
+ return None
+
+ return synapse_root
+
+ # Read the `direct_url.json` metadata for the package. This won't exist for
+ # packages installed via a repository/etc.
+ # c.f. https://packaging.python.org/en/latest/specifications/direct-url/
+ direct_url_json = package.read_text("direct_url.json")
+ if direct_url_json is None:
+ return None
+
+ # c.f. https://packaging.python.org/en/latest/specifications/direct-url/ for
+ # the format
+ direct_url_dict: dict = json.loads(direct_url_json)
+
+ # `url` must exist as a key, and point to where we fetched the repo from.
+ project_url = urllib.parse.urlparse(direct_url_dict["url"])
+
+ # If its not a local file then we must have built the rust libs either a)
+ # after we downloaded the package, or b) we built the download wheel.
+ if project_url.scheme != "file":
+ return None
+
+ # And finally if its not an editable install then the files can't have
+ # changed since we installed the package.
+ if not direct_url_dict.get("dir_info", {}).get("editable", False):
+ return None
+
+ return project_url.path
|