Skip to content

Commit 9020fd2

Browse files
[BE] Remind users to update submodule
Summary: As titled. This adds messages when we suspect the user forgot to update git submodule or cleanup the CMake cache. Test Plan: See the following error messages Reviewers: Subscribers: Tasks: Tags: ghstack-source-id: 6236566b68a6df7d485ef74566d53c67a771eace Pull Request resolved: #8203 Co-authored-by: Mengwei Liu <[email protected]>
1 parent 7805229 commit 9020fd2

File tree

2 files changed

+167
-31
lines changed

2 files changed

+167
-31
lines changed

build/extract_sources.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import argparse
99
import copy
10+
import logging
1011
import os
1112
import re
1213

@@ -18,7 +19,7 @@
1819
try:
1920
import tomllib # Standard in 3.11 and later
2021
except ModuleNotFoundError:
21-
import tomli as tomllib
22+
import tomli as tomllib # type: ignore[no-redef]
2223

2324
"""Extracts source lists from the buck2 build system and writes them to a file.
2425
@@ -66,6 +67,12 @@
6667
]
6768
"""
6869

70+
# Set up logging
71+
logging.basicConfig(
72+
level=logging.INFO, format="%(asctime)s [ExecuTorch] %(levelname)s: %(message)s"
73+
)
74+
logger = logging.getLogger()
75+
6976

7077
class Target:
7178
"""Parsed [targets.*] entry from the TOML file.
@@ -118,7 +125,23 @@ def get_sources(
118125
)
119126

120127
# Get the complete list of source files that this target depends on.
121-
sources: set[str] = set(runner.run(["cquery", query] + buck_args))
128+
# If user doesn't setup their git submodules correctly, this will fail.
129+
# If we hit here, setup.py:check_submodule() should have already run
130+
# but it could be that the submodules are not synced or there's local changes.
131+
try:
132+
sources: set[str] = set(runner.run(["cquery", query] + buck_args))
133+
except RuntimeError as e:
134+
logger.error(
135+
f"\033[31;1mFailed to query buck for sources. Failed command:\n\n"
136+
f" buck2 cquery {query} {' '.join(buck_args)}\n\n"
137+
"This is likely due "
138+
"to missing git submodules or outdated CMake cache. "
139+
"Please run the following before retry:\033[0m\n\n"
140+
" \033[32;1m./install_executorch.sh --clean\033[0m\n"
141+
" \033[32;1mgit submodule sync\033[0m\n"
142+
" \033[32;1mgit submodule update --init\033[0m\n"
143+
)
144+
raise e
122145

123146
# Keep entries that match all of the filters.
124147
filters = [re.compile(p) for p in self._config.get("filters", [])]

setup.py

+142-29
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
# Import this before distutils so that setuptools can intercept the distuils
5656
# imports.
5757
import setuptools # noqa: F401 # usort: skip
58+
import logging
59+
import subprocess
5860

5961
from distutils import log
6062
from distutils.sysconfig import get_python_lib
@@ -66,9 +68,91 @@
6668
from setuptools.command.build_ext import build_ext
6769
from setuptools.command.build_py import build_py
6870

71+
# Set up logging
72+
logging.basicConfig(
73+
level=logging.INFO, format="%(asctime)s [ExecuTorch] %(levelname)s: %(message)s"
74+
)
75+
logger = logging.getLogger()
76+
6977
# For information on setuptools Command subclassing see
7078
# https://setuptools.pypa.io/en/latest/userguide/extension.html
7179

80+
################################################################################
81+
# Git submodules
82+
################################################################################
83+
# The following submodules are required to be able to build ExecuTorch. If any of
84+
# these folders are missing or missing CMakeLists.txt, we will run
85+
# `git submodule update` to try to fix it. If the command fails, we will raise an
86+
# error.
87+
# An alternative to this would be to run `git submodule status` and run
88+
# `git submodule update` if there's any local changes. However this is a bit
89+
# too restrictive for users who modifies and tests the dependencies locally.
90+
91+
# keep sorted
92+
REQUIRED_SUBMODULES = {
93+
"ao": "LICENSE", # No CMakeLists.txt, choose a sort of stable file to check.
94+
"cpuinfo": "CMakeLists.txt",
95+
"eigen": "CMakeLists.txt",
96+
"flatbuffers": "CMakeLists.txt",
97+
"FP16": "CMakeLists.txt",
98+
"FXdiv": "CMakeLists.txt",
99+
"gflags": "CMakeLists.txt",
100+
"prelude": "BUCK",
101+
"pthreadpool": "CMakeLists.txt",
102+
"pybind11": "CMakeLists.txt",
103+
"XNNPACK": "CMakeLists.txt",
104+
}
105+
106+
107+
def get_required_submodule_paths():
108+
gitmodules_path = os.path.join(os.getcwd(), ".gitmodules")
109+
110+
if not os.path.isfile(gitmodules_path):
111+
logger.error(".gitmodules file not found.")
112+
exit(1)
113+
114+
with open(gitmodules_path, "r") as file:
115+
lines = file.readlines()
116+
117+
# Extract paths of required submodules
118+
required_paths = {}
119+
for line in lines:
120+
if line.strip().startswith("path ="):
121+
path = line.split("=")[1].strip()
122+
for submodule, file_name in REQUIRED_SUBMODULES.items():
123+
if submodule in path:
124+
required_paths[path] = file_name
125+
return required_paths
126+
127+
128+
def check_and_update_submodules():
129+
def check_folder(folder: str, file: str) -> bool:
130+
return os.path.isdir(folder) and os.path.isfile(os.path.join(folder, file))
131+
132+
# Check if the directories exist for each required submodule
133+
missing_submodules = {}
134+
for path, file in get_required_submodule_paths().items():
135+
if not check_folder(path, file):
136+
missing_submodules[path] = file
137+
138+
# If any required submodule directories are missing, update them
139+
if missing_submodules:
140+
logger.warning("Some required submodules are missing. Updating submodules...")
141+
try:
142+
subprocess.check_call(["git", "submodule", "sync"])
143+
subprocess.check_call(["git", "submodule", "update", "--init"])
144+
except subprocess.CalledProcessError as e:
145+
logger.error(f"Error updating submodules: {e}")
146+
exit(1)
147+
148+
# After updating submodules, check again
149+
for path, file in missing_submodules.items():
150+
if not check_folder(path, file):
151+
logger.error(f"{file} not found in {path}.")
152+
logger.error("Please run `git submodule update --init`.")
153+
exit(1)
154+
logger.info("All required submodules are present.")
155+
72156

73157
class ShouldBuild:
74158
"""Indicates whether to build various components."""
@@ -638,7 +722,28 @@ def run(self):
638722
# lists.
639723

640724
# Generate the build system files.
641-
self.spawn(["cmake", "-S", repo_root, "-B", cmake_cache_dir, *cmake_args])
725+
try:
726+
subprocess.run(
727+
["cmake", "-S", repo_root, "-B", cmake_cache_dir, *cmake_args],
728+
stdout=subprocess.PIPE,
729+
stderr=subprocess.PIPE,
730+
check=True,
731+
text=True,
732+
)
733+
except subprocess.CalledProcessError as e:
734+
error = str(e.stderr)
735+
# Our educated guesses from parsing the error message.
736+
# Missing source file, could be related to git submodules not synced or cmake cache is outdated
737+
additional_log = ""
738+
if "Cannot find source file" in error:
739+
additional_log = (
740+
"\033[31;1mEither CMake cache is outdated or git submodules are not synced.\n"
741+
"Please run the following before retry:\033[0m\n"
742+
" \033[32;1m./install_executorch.sh --clean\033[0m\n"
743+
" \033[32;1mgit submodule sync\033[0m\n"
744+
" \033[32;1mgit submodule update --init\033[0m\n"
745+
)
746+
raise Exception(error + "\n" + additional_log) from e
642747

643748
# Build the system.
644749
self.spawn(["cmake", "--build", cmake_cache_dir, *build_args])
@@ -720,31 +825,39 @@ def get_ext_modules() -> List[Extension]:
720825
return ext_modules
721826

722827

723-
setup(
724-
version=Version.string(),
725-
# TODO(dbort): Could use py_modules to restrict the set of modules we
726-
# package, and package_data to restrict the set up non-python files we
727-
# include. See also setuptools/discovery.py for custom finders.
728-
package_dir={
729-
"executorch/backends": "backends",
730-
"executorch/codegen": "codegen",
731-
# TODO(mnachin T180504136): Do not put examples/models
732-
# into core pip packages. Refactor out the necessary utils
733-
# or core models files into a separate package.
734-
"executorch/examples/models": "examples/models",
735-
"executorch/exir": "exir",
736-
"executorch/extension": "extension",
737-
"executorch/kernels/quantized": "kernels/quantized",
738-
"executorch/schema": "schema",
739-
"executorch/devtools": "devtools",
740-
"executorch/devtools/bundled_program": "devtools/bundled_program",
741-
"executorch/runtime": "runtime",
742-
"executorch/util": "util",
743-
},
744-
cmdclass={
745-
"build": CustomBuild,
746-
"build_ext": InstallerBuildExt,
747-
"build_py": CustomBuildPy,
748-
},
749-
ext_modules=get_ext_modules(),
750-
)
828+
def main():
829+
# Check submodules
830+
check_and_update_submodules()
831+
832+
setup(
833+
version=Version.string(),
834+
# TODO(dbort): Could use py_modules to restrict the set of modules we
835+
# package, and package_data to restrict the set up non-python files we
836+
# include. See also setuptools/discovery.py for custom finders.
837+
package_dir={
838+
"executorch/backends": "backends",
839+
"executorch/codegen": "codegen",
840+
# TODO(mnachin T180504136): Do not put examples/models
841+
# into core pip packages. Refactor out the necessary utils
842+
# or core models files into a separate package.
843+
"executorch/examples/models": "examples/models",
844+
"executorch/exir": "exir",
845+
"executorch/extension": "extension",
846+
"executorch/kernels/quantized": "kernels/quantized",
847+
"executorch/schema": "schema",
848+
"executorch/devtools": "devtools",
849+
"executorch/devtools/bundled_program": "devtools/bundled_program",
850+
"executorch/runtime": "runtime",
851+
"executorch/util": "util",
852+
},
853+
cmdclass={
854+
"build": CustomBuild,
855+
"build_ext": InstallerBuildExt,
856+
"build_py": CustomBuildPy,
857+
},
858+
ext_modules=get_ext_modules(),
859+
)
860+
861+
862+
if __name__ == "__main__":
863+
main()

0 commit comments

Comments
 (0)