Skip to content

Commit 793b865

Browse files
partoufclaude
andcommitted
Parse cmake link.txt files for library artifact detection
Instead of guessing library type from bare cmake target names, parse the link.txt files that cmake generates during configure to find actual library artifacts (e.g. libminiz.a, libfoo.so). This enables accurate auto-detection of static vs shared vs packaged-headers library types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3a6e026 commit 793b865

1 file changed

Lines changed: 53 additions & 9 deletions

File tree

core/library_utils.py

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,16 @@ def analyze_repository_structure(clone_path: Path) -> dict:
4949
{
5050
'has_cmake': bool,
5151
'cmake_targets': list[str] | None,
52-
'main_targets': list[str] | None
52+
'main_targets': list[str] | None,
53+
'library_artifacts': list[str] # e.g. ["libminiz.a"]
5354
}
5455
"""
55-
analysis = {"has_cmake": False, "cmake_targets": None, "main_targets": None}
56+
analysis = {
57+
"has_cmake": False,
58+
"cmake_targets": None,
59+
"main_targets": None,
60+
"library_artifacts": [],
61+
}
5662

5763
try:
5864
# Check for CMakeLists.txt existence
@@ -61,12 +67,16 @@ def analyze_repository_structure(clone_path: Path) -> dict:
6167

6268
# Try to get CMake targets if CMakeLists.txt exists
6369
if analysis["has_cmake"]:
64-
analysis["cmake_targets"] = get_cmake_targets_from_path(clone_path)
70+
build_path = clone_path / "build"
71+
analysis["cmake_targets"] = get_cmake_targets_from_path(clone_path, build_path)
6572
if analysis["cmake_targets"]:
6673
analysis["main_targets"] = filter_main_cmake_targets(analysis["cmake_targets"])
74+
analysis["library_artifacts"] = get_library_artifacts_from_build(build_path)
6775
logger.info(f"Found {len(analysis['cmake_targets'])} CMake targets")
6876
if analysis["main_targets"]:
6977
logger.info(f"Filtered to {len(analysis['main_targets'])} main targets")
78+
if analysis["library_artifacts"]:
79+
logger.info(f"Found library artifacts: {analysis['library_artifacts']}")
7080

7181
return analysis
7282

@@ -97,19 +107,19 @@ def clone_and_analyze_repository(github_url: str) -> tuple[bool, dict]:
97107
return True, analysis
98108

99109

100-
def get_cmake_targets_from_path(clone_path: Path) -> list[str] | None:
110+
def get_cmake_targets_from_path(clone_path: Path, build_path: Path) -> list[str] | None:
101111
"""
102112
Get CMake targets from an already cloned repository path.
103113
104114
Args:
105115
clone_path: Path to the cloned repository
116+
build_path: Path to use for the cmake build directory
106117
107118
Returns:
108119
List of library target names, or None on error
109120
"""
110121
try:
111122
# Configure CMake
112-
build_path = clone_path / "build"
113123
result = run_command(
114124
["cmake", "-B", str(build_path), "-S", str(clone_path)], clean_env=False
115125
)
@@ -156,6 +166,35 @@ def get_cmake_targets_from_path(clone_path: Path) -> list[str] | None:
156166
return None
157167

158168

169+
def get_library_artifacts_from_build(build_path: Path) -> list[str]:
170+
"""
171+
Parse cmake link.txt files to find library artifacts (.a/.so) that will be produced.
172+
173+
After cmake configure, each linkable target has a link.txt in
174+
build/CMakeFiles/<target>.dir/link.txt containing the link command.
175+
176+
Returns list of artifact filenames (e.g., ["libminiz.a", "libfoo.so"]).
177+
"""
178+
artifacts = []
179+
link_files = list(build_path.glob("CMakeFiles/*.dir/link.txt"))
180+
logger.debug(f"Found {len(link_files)} link.txt files in {build_path}")
181+
182+
for link_file in link_files:
183+
try:
184+
content = link_file.read_text(encoding="utf-8", errors="replace")
185+
tokens = content.split()
186+
for token in tokens:
187+
filename = Path(token).name
188+
if filename.startswith("lib") and (filename.endswith(".a") or ".so" in filename):
189+
if filename not in artifacts:
190+
artifacts.append(filename)
191+
except Exception as e:
192+
logger.debug(f"Error reading {link_file}: {e}")
193+
194+
logger.debug(f"Found library artifacts: {artifacts}")
195+
return artifacts
196+
197+
159198
def filter_main_cmake_targets(targets: list[str]) -> list[str]:
160199
"""
161200
Filter CMake targets to include only main library targets.
@@ -261,8 +300,8 @@ def validate_cmake_targets_for_type(analysis: dict, library_type: LibraryType) -
261300
- is_ok=True: validation passed (message may be informational)
262301
- is_ok=False: validation failed (message describes the error)
263302
"""
264-
targets = analysis.get("cmake_targets", [])
265-
has_library_artifacts = any(t.endswith(".a") or t.endswith(".so") for t in targets)
303+
artifacts = analysis.get("library_artifacts", [])
304+
has_library_artifacts = len(artifacts) > 0
266305

267306
if library_type in (LibraryType.STATIC, LibraryType.SHARED, LibraryType.CSHARED):
268307
if not has_library_artifacts:
@@ -346,8 +385,13 @@ def detect_library_type_from_analysis(
346385

347386
# Detect based on repository analysis
348387
if analysis.get("has_cmake"):
349-
# Has CMakeLists.txt, assume it's a packaged-headers library
350-
return True, LibraryType.PACKAGED_HEADERS.value
388+
artifacts = analysis.get("library_artifacts", [])
389+
if any(a.endswith(".a") for a in artifacts):
390+
return True, LibraryType.STATIC.value
391+
elif any(a.endswith(".so") or ".so." in a for a in artifacts):
392+
return True, LibraryType.SHARED.value
393+
else:
394+
return True, LibraryType.PACKAGED_HEADERS.value
351395
else:
352396
# No CMakeLists.txt, could be header-only or require manual configuration
353397
return True, LibraryType.HEADER_ONLY.value

0 commit comments

Comments
 (0)