mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-28 21:14: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`
|
/// 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(|| {
|
static MISSING_HEADER_RE: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::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()
|
.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)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -76,9 +80,15 @@ pub enum Error {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MissingLibrary {
|
||||||
|
Header(String),
|
||||||
|
Linker(String),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub struct MissingHeaderCause {
|
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
|
// 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
|
// look for the build dependencies of that version or git commit respectively
|
||||||
package_id: String,
|
package_id: String,
|
||||||
|
@ -86,12 +96,24 @@ pub struct MissingHeaderCause {
|
||||||
|
|
||||||
impl Display for MissingHeaderCause {
|
impl Display for MissingHeaderCause {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match &self.missing_library {
|
||||||
|
MissingLibrary::Header(header) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"This error likely indicates that you need to install a library that provides \"{}\" for {}",
|
"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 {
|
impl Error {
|
||||||
|
@ -103,19 +125,28 @@ impl Error {
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).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
|
// In the cases i've seen it was the 5th and 3rd last line (see test case), 10 seems like a reasonable cutoff
|
||||||
// reasonable cutoff
|
let missing_library = stderr.lines().rev().take(10).find_map(|line| {
|
||||||
if let Some(header) =
|
if let Some((_, [header])) =
|
||||||
stderr.lines().rev().take(10).find_map(|line| {
|
MISSING_HEADER_RE.captures(line.trim()).map(|c| c.extract())
|
||||||
Some(MISSING_HEADER_RE.captures(line.trim())?["header"].to_string())
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
|
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 {
|
return Self::MissingHeader {
|
||||||
message,
|
message,
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
stderr,
|
||||||
missing_header_cause: MissingHeaderCause {
|
missing_header_cause: MissingHeaderCause {
|
||||||
header,
|
missing_library,
|
||||||
package_id: package_id.into(),
|
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"###
|
@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