mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-28 13:04:47 +00:00
Explain ld errors (#773)
One of the most common ways source dists fail to build (on linux) is when the linker fails because the shared library of a native dependency is not installed. These errors are hard to understand when you're not a c programmer: ``` In file included from /usr/include/python3.10/unicodeobject.h:1046, from /usr/include/python3.10/Python.h:83, from Modules/3.x/readline.c:8: Modules/3.x/readline.c: In function ‘on_completion’: /usr/include/python3.10/cpython/unicodeobject.h:744:29: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] 744 | #define _PyUnicode_AsString PyUnicode_AsUTF8 | ^~~~~~~~~~~~~~~~ Modules/3.x/readline.c:842:23: note: in expansion of macro ‘_PyUnicode_AsString’ 842 | char *s = _PyUnicode_AsString(r); | ^~~~~~~~~~~~~~~~~~~ Modules/3.x/readline.c: In function ‘readline_until_enter_or_signal’: Modules/3.x/readline.c:1044:9: warning: ‘sigrelse’ is deprecated: Use the sigprocmask function instead [-Wdeprecated-declarations] 1044 | sigrelse(SIGINT); | ^~~~~~~~ In file included from Modules/3.x/readline.c:10: /usr/include/signal.h:359:12: note: declared here 359 | extern int sigrelse (int __sig) __THROW | ^~~~~~~~ Modules/3.x/readline.c: In function ‘PyInit_readline’: Modules/3.x/readline.c:1179:34: warning: assignment to ‘char * (*)(FILE *, FILE *, const char *)’ from incompatible pointer type ‘char * (*)(FILE *, FILE *, char *)’ [-Wincompatible-pointer-types] 1179 | PyOS_ReadlineFunctionPointer = call_readline; | ^ In file included from /usr/include/string.h:535, from /usr/include/python3.10/Python.h:30, from Modules/3.x/readline.c:8: In function ‘strncpy’, inlined from ‘call_readline’ at Modules/3.x/readline.c:1124:9: /usr/include/x86_64-linux-gnu/bits/string_fortified.h:95:10: warning: ‘__builtin_strncpy’ output truncated before terminating nul copying as many bytes from a string as its length [-Wstringop-truncation] 95 | return __builtin___strncpy_chk (__dest, __src, __len, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 96 | __glibc_objsize (__dest)); | ~~~~~~~~~~~~~~~~~~~~~~~~~ Modules/3.x/readline.c: In function ‘call_readline’: Modules/3.x/readline.c:1099:9: note: length computed here 1099 | n = strlen(p); | ^~~~~~~~~ /usr/bin/ld: cannot find -lncurses: No such file or directory collect2: error: ld returned 1 exit status error: command '/usr/bin/x86_64-linux-gnu-gcc' failed with exit code 1 --- ``` We parse these errors out, tell the user about the missing shared library and even the most likely debian/ubuntu package name: ``` This error likely indicates that you need to install the library that provides a shared library for ncurses for pygraphviz-1.11 (e.g. libncurses-dev) ```
This commit is contained in:
parent
8ac6f9a198
commit
57c96df288
1 changed files with 89 additions and 16 deletions
|
@ -34,10 +34,14 @@ use puffin_traits::{BuildContext, BuildKind, SourceBuildTrait};
|
|||
/// e.g. `pygraphviz/graphviz_wrap.c:3020:10: fatal error: graphviz/cgraph.h: No such file or directory`
|
||||
static MISSING_HEADER_RE: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(
|
||||
r".*\.(c|c..|h|h..):\d+:\d+: fatal error: (?<header>.*\.(h|h..)): No such file or directory"
|
||||
r".*\.(?:c|c..|h|h..):\d+:\d+: fatal error: (.*\.(?:h|h..)): No such file or directory",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
/// e.g. `/usr/bin/ld: cannot find -lncurses: No such file or directory`
|
||||
static LD_NOT_FOUND_RE: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"/usr/bin/ld: cannot find -l([a-zA-Z10-9]+): No such file or directory").unwrap()
|
||||
});
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
|
@ -76,9 +80,15 @@ pub enum Error {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MissingLibrary {
|
||||
Header(String),
|
||||
Linker(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct MissingHeaderCause {
|
||||
header: String,
|
||||
missing_library: MissingLibrary,
|
||||
// I've picked this over the better readable package name to make clear that you need to
|
||||
// look for the build dependencies of that version or git commit respectively
|
||||
package_id: String,
|
||||
|
@ -86,12 +96,24 @@ pub struct MissingHeaderCause {
|
|||
|
||||
impl Display for MissingHeaderCause {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.missing_library {
|
||||
MissingLibrary::Header(header) => {
|
||||
write!(
|
||||
f,
|
||||
"This error likely indicates that you need to install a library that provides \"{}\" for {}",
|
||||
self.header, self.package_id
|
||||
header, self.package_id
|
||||
)
|
||||
}
|
||||
MissingLibrary::Linker(library) => {
|
||||
write!(
|
||||
f,
|
||||
"This error likely indicates that you need to install the library that provides a shared library \
|
||||
for {library} for {package_id} (e.g. lib{library}-dev)",
|
||||
library=library, package_id=self.package_id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -103,19 +125,28 @@ impl Error {
|
|||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
|
||||
// In the cases i've seen it was the 5th last line (see test case), 10 seems like a
|
||||
// reasonable cutoff
|
||||
if let Some(header) =
|
||||
stderr.lines().rev().take(10).find_map(|line| {
|
||||
Some(MISSING_HEADER_RE.captures(line.trim())?["header"].to_string())
|
||||
})
|
||||
// In the cases i've seen it was the 5th and 3rd last line (see test case), 10 seems like a reasonable cutoff
|
||||
let missing_library = stderr.lines().rev().take(10).find_map(|line| {
|
||||
if let Some((_, [header])) =
|
||||
MISSING_HEADER_RE.captures(line.trim()).map(|c| c.extract())
|
||||
{
|
||||
Some(MissingLibrary::Header(header.to_string()))
|
||||
} else if let Some((_, [library])) =
|
||||
LD_NOT_FOUND_RE.captures(line.trim()).map(|c| c.extract())
|
||||
{
|
||||
Some(MissingLibrary::Linker(library.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(missing_library) = missing_library {
|
||||
return Self::MissingHeader {
|
||||
message,
|
||||
stdout,
|
||||
stderr,
|
||||
missing_header_cause: MissingHeaderCause {
|
||||
header,
|
||||
missing_library,
|
||||
package_id: package_id.into(),
|
||||
},
|
||||
};
|
||||
|
@ -740,4 +771,46 @@ mod test {
|
|||
@r###"This error likely indicates that you need to install a library that provides "graphviz/cgraph.h" for pygraphviz-1.11"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_linker_library() {
|
||||
let output = Output {
|
||||
status: ExitStatus::default(), // This is wrong but `from_raw` is platform-gated.
|
||||
stdout: Vec::new(),
|
||||
stderr: indoc!(
|
||||
r"
|
||||
1099 | n = strlen(p);
|
||||
| ^~~~~~~~~
|
||||
/usr/bin/ld: cannot find -lncurses: No such file or directory
|
||||
collect2: error: ld returned 1 exit status
|
||||
error: command '/usr/bin/x86_64-linux-gnu-gcc' failed with exit code 1
|
||||
"
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
};
|
||||
|
||||
let err = Error::from_command_output(
|
||||
"Failed building wheel through setup.py".to_string(),
|
||||
&output,
|
||||
"pygraphviz-1.11",
|
||||
);
|
||||
assert!(matches!(err, Error::MissingHeader { .. }));
|
||||
insta::assert_display_snapshot!(err, @r###"
|
||||
Failed building wheel through setup.py:
|
||||
--- stdout:
|
||||
|
||||
--- stderr:
|
||||
1099 | n = strlen(p);
|
||||
| ^~~~~~~~~
|
||||
/usr/bin/ld: cannot find -lncurses: No such file or directory
|
||||
collect2: error: ld returned 1 exit status
|
||||
error: command '/usr/bin/x86_64-linux-gnu-gcc' failed with exit code 1
|
||||
---
|
||||
"###);
|
||||
insta::assert_display_snapshot!(
|
||||
std::error::Error::source(&err).unwrap(),
|
||||
@"This error likely indicates that you need to install the library that provides a shared library for ncurses for pygraphviz-1.11 (e.g. libncurses-dev)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue