#!/usr/bin/perl use strict; use warnings; use IPC::Open2; use Cwd; use constant DOCKER => -e '/.dockerenv'; my $pwd = getcwd(); # how to run poldek my @ignore = qw(vserver-packages python-devel-src); my @poldek; if (DOCKER) { push(@poldek, qw[poldek]); } else { push(@poldek, qw[sudo poldek -n th-x86_64-ready], "--cachedir=$pwd/poldekcache", "--conf=$pwd/poldekconf/poldek.conf", ); } push(@poldek, ( "--skip-installed", "-O", "ignore=" . (join " ", @ignore) )); # if multiple packages provide some funcionality those will be selected: my %preferred = ( "automake" => +1, "autoconf" => +1, "pkgconfig" => +1, "python" => +1, "python-modules" => +1, "python-setuptools" => +1, "perl-modules" => +1, "perl-Encode" => +2, "zlib-devel" => +1, "libstdc++-devel" => +1, "libusb-compat-devel" => +1, "libjpeg-devel" => +1, "libpng-devel" => +1, "libsamplerate-devel" => +1, "pulseaudio-devel" => +1, "xorg-lib-libXrandr-devel" => +1, "sqlite-devel" => +1, "ice-devel" => -1, ); # translate package name to provides name my %translate = ( "gcc-c++" => "libstdc++-devel", "rarian-compat" => "scrollkeeper", "Mesa-libGL" => "OpenGL", "Mesa-libGL-devel" => "OpenGL-devel", "Mesa-libGLU-devel" => "OpenGL-GLU-devel", ); my @skip = qw(hostname git svn svnversion mt gawk); my %skip; @skip{ @skip } = (1) x scalar @skip; # for m4 in *.m4; do R=$(rpm -qf $m4); R=${R%-*-*}; \ # awk -vr=$R '/^\s*(AC_DEFUN|AU_ALIAS)/ { gsub(/\].*/,""); gsub(/.*\[/,""); print r " " $0}' $m4; \ # done | sort | awk '{print "\t\"" $2 "\" => \"" $1 "\","}' my %ac2br = do (DOCKER ? './cleanbuild/findbr-ac2br' : './findbr-ac2br'); my %cmake2br = ( "findkde4:44" => "kde4-kdelibs", "findmsgfmt" => "gettext-tools", "findpythoninterp" => "python", ); BEGIN { $SIG{__WARN__} = sub { local $_ = shift; chomp; print STDERR "\033[31;1m" . $_ . "\033[0m\n" }; } my $builddir = shift @ARGV; my @lines = ; my $reason; my %out; sub add_br { my $pkg = shift; my $msg = shift || $reason; if ( ref $pkg ) { foreach my $p ( @$pkg ) { add_br( $p, $msg ); } return; } $msg =~ s/\n/ # /sg; $pkg = $translate{ $pkg } || $pkg; return if exists $out{ $pkg }; $out{ $pkg } = $msg; print STDERR "\033[33;1madding: $pkg => $msg\033[0m\n"; } sub poldek_cmd { my $cmd = shift; warn "Poldek: $cmd\n"; my @cmd = (@poldek, "--shcmd=".$cmd); open my $fh, '-|', @cmd or die "$!: @cmd"; my @read = <$fh>; close $fh or die $!; return wantarray ? @read : \@read; } my $check_ac = 0; my $check_config_log = undef; my $check_mkmf_log = undef; my %checked_files; sub poldek_file { my @files; foreach ( @_ ) { next if $checked_files{ $_ }; $checked_files{ $_ } = 1; push @files, $_; } return unless @files; my $search = join "; ", map "search -f $_", @files; warn "Reason: $reason\n"; my @read = poldek_cmd( $search ); local $_; my @found; while ( $_ = shift @read ) { if ( /(\d+) package\(s\) found:$/ ) { foreach my $i ( 1..$1 ) { $_ = shift @read; chomp; $_ =~ /^(.*)-.*?-.*?$/; push @found, $1; } } } return unless @found; my $found = $found[0]; if ( @found > 1 ) { my $i = 0.0; my %pts = map { ( $_ => ( ($i += 0.001) + ( $preferred{ $_ } || 0 ) ) ) } reverse @found; my @pref = sort { $pts{$b} <=> $pts{$a} } @found; my $pref = join ", ", map "$_ => $pts{$_}", @pref; warn "Multiple found: $pref\n"; $found = $pref[0]; } $found = $translate{ $found } if $translate{ $found }; add_br( $found ); } my $pkglist; sub get_pkglist { my %pkglist; my $read = poldek_cmd( "ls" ); foreach ( @$read ) { chomp; next unless /(\S+)-.*?-.*?\.[a-z0-9_]+$/; my $pkg = $1; $pkglist{ lc $pkg } = $pkg; } $pkglist = \%pkglist; } sub guess_package { my $origname = shift; get_pkglist() unless $pkglist; return unless defined $origname; my $name = lc $origname; my @try = ( "lib$name-devel", "$name-devel", "$name", "lib$name-.*-devel", "$name-.*-devel", ".*-lib$name-devel", ".*-$name-devel", ".*-lib$name-.*-devel", ".*-$name-.*-devel", ); my @select; for my $k ( keys %$pkglist ) { if ( $k =~ /$name/ ) { push @select, $k; } } @select = sort @select; foreach my $try ( @try ) { foreach my $pkg ( @select ) { if ( $pkg =~ /^$try$/ ) { warn "guessed: $origname => $pkglist->{ $pkg }\n"; return $pkglist->{ $pkg }; } } } warn "$origname not guessed\n"; return undef; } start_check: my %checked; my $cmake_get_call = 0; my $cmake_pkg_list = 0; my $py_ver = undef; while ( $_ = shift @lines ) { chomp; #next if $checked{ $_ }; #$checked{ $_ } = 1; # try to extract current python version from setup.py run if (m{^copying .+ -> build-(\d+)/lib/}) { $py_ver = $1; warn "py_ver set to '$py_ver'\n"; } $reason = $_; if ( /^\S+: (\S+): (?:Command )?not found$/ or /.*configure\[\d+\]: (\S+): not found$/ or m{which: no (\S+) in \(.*/bin.*\)} or m{\S+: (\S+): command not found$} or m{(?:/usr/bin/)?env: (\S+): No such file or directory} or m{flock: (\S+): No such file or directory} or m{Can't exec "(\S+)": No such file or directory} ) { my $exec = $1; $exec = $1 if $exec =~ m{^"(.*)"$}; next if $skip{ $exec }; warn "Looking for executable $exec\n"; if ( $exec =~ m#^/# ) { poldek_file( $exec ); } poldek_file( "/usr/bin/$exec", "/bin/$exec" ); } if ( /\S+\.[ch](?:pp|xx|c)?:\d+:\d+: error: (\S+): No such file or directory$/ or /\S+\.[ch](?:pp|xx|c)?:\d+:\d+: fatal error: (\S+): No such file or directory$/ or /Can't find the '(\S+\.h)'? header/ ) { my $h = $1; warn "Looking for C(++) header $h\n"; poldek_file( "/usr/include*/$h" ); } if (m{^ImportError: No module named (\S+)$} or m{^ERROR: Cannot find .+: No module named (\S+)$} or m{^ERROR: Failed to import the ".+" module: No module named (\S+)$} or m{^distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse\('([^'>=]+).*'\)} or m{^error: Could not find suitable distribution for Requirement.parse\('([^'>=]+).*'\)} or m{^Couldn't find index page for '(\S+)'} ) { my $mod = $1; $mod =~ s#\.#/#g; warn "Looking for Python$py_ver module $mod\n"; poldek_file( "/usr/share/python$py_ver*/site-packages/$mod/__init__.py*", "/usr/share/python$py_ver*/$mod/__init__.py*", "/usr/share/python$py_ver*/site-packages/$mod.py*", "/usr/share/python$py_ver*/$mod.py*", "/usr/lib*/python$py_ver*/site-packages/$mod/__init__.py*", "/usr/lib*/python$py_ver*/$mod/__init__.py*", "/usr/lib*/python$py_ver*/site-packages/$mod.py*", "/usr/lib*/python$py_ver*/$mod.py*", "/usr/lib*/python$py_ver*/site-packages/_$mod.so", "/usr/lib*/python$py_ver*/_$mod.so", "/usr/lib*/python$py_ver*/site-packages/$mod.so", "/usr/lib*/python$py_ver*/$mod.so", ); } if ( m{^-- Could NOT find Sphinx \(missing: SPHINX_EXECUTABLE\)} ) { add_br("sphinx-pdg"); next; } if ( m{^You need to install the (\S+) module} or m{\*\*\*Error\*\*\*: You must have (\S+) >= (\S+) installed} ) { add_br($1); next; } if ( m{^cannot load such file -- (\S+)} or m{in `require': cannot load such file -- (\S+) \(LoadError\)} ) { my $mod = $1; warn "Looking for ruby module $mod\n"; poldek_file( "/usr/share/ruby/*/$mod.rb", "/usr/share/ruby/vendor_ruby/*/$mod.rb", "/usr/lib64/ruby/vendor_ruby/*/$mod.so", ); } if ( /configure(?:\.in|\.ac)?:\d+: error: possibly undefined macro: (\S+)/ or m{configure(?:\.in|\.ac)?:\d+: error: m4 macro `(\S+)' is not defined} or m{warning: macro `(\S+)' not found in library} ) { my $macro = $1; warn "Looking for autotools macro $macro\n"; if ( my $br = $ac2br{ $macro } ) { add_br( $br ); next; } else { $check_ac = 1; } } if ( /^No package '(\S+)' found$/ or /Package (\S+) was not found in the pkg-config search path/ or m{None of the required '(\S+?)(?:[<>=].*)?' found} or m{--\s+package '(\S+?)(?:[<>=].*)?' not found} or m{ERROR: cannot find a valid pkg-config package for \['(\S+?)(?:[<>=].*)?'\]} ) { my $pkg = $1; warn "Looking for package $pkg\n"; poldek_file( "/usr/lib*/pkgconfig/$pkg.pc", "/usr/share/pkgconfig/$pkg.pc" ); } if (/^ocamlfind: Package `(\S+)' not found - required by/ or m{Camlp4: Uncaught exception: DynLoader.Error \("(\S+)", "file not found in path"\)} ) { my $pkg = $1; warn "Looking for ocaml package $pkg\n"; poldek_file( "/usr/lib*/ocaml/*/$pkg.a", "/usr/lib*/ocaml/*/$pkg"); } if ( m{^cp: cannot stat `(/.*)': No such file or directory$} ) { my $f = $1; warn "Looking for file $f\n"; poldek_file( $f ); } if ( m{ERROR: Could not find KDE4 (kde4-config)}) { my $f = "/usr/bin/$1"; warn "Looking for file $f\n"; poldek_file( $f ); } if (m{Ragel State Machine Compiler not found}) { add_br( "ragel" ); next; } if (m{ERROR: CMake is required to build}) { add_br("cmake"); next; } # lucky guessing if (m{'yum install (\S+)'}) { add_br($1); next; } if ( m{^find-lang.sh: Error: international files not found for '} or m{ gettext tools not found} ) { add_br( "gettext-tools" ); next; } if ( m{ pkg-config .*not .*found} or m{^checking for pkg-config\.\.\. no} or m{^pkg-config unavailable, build terminated} or m{^\*\*\*Error\*\*\*: You must have pkg-config >= .* installed} or m{^-- Could NOT find PkgConfig \(missing: PKG_CONFIG_EXECUTABLE\)} or m{exec: "pkg-config": executable file not found in \$PATH} ) { add_br( "pkgconfig" ); next; } if ( m{Can't locate (.*?\.pm) in \@INC} ) { my $mod = $1; warn "Looking for perl module $mod\n"; poldek_file( "/usr/lib*/perl*/$mod", "/usr/share/perl*/$mod" ); } if ( m{^(?:/usr/bin/ld: )?cannot find -l(.*?)$} ) { my $lib = $1; warn "Looking for library $lib\n"; poldek_file( "/usr/lib64/lib$lib.so", "/usr/lib/lib$lib.so", "/usr/lib*/lib$lib.so" ); } # full path to ldd files if ( m{^WARNING; can.*t resolve .* dependency: (.*?)$} ) { my $lib = $1; warn "Looking for library '$lib'\n"; poldek_file( "/usr/lib64/$lib", "/usr/lib/$lib", "/lib64/$lib", "/lib/$lib"); } if ( m{^error: Couldn't exec (/\S+): No such file or directory$} or m{^Can't open perl script "(/\S+)": No such file or directory$} or m{unable to open (/\S+) \(No such file or directory\)$} or m{GConf-CRITICAL \*\*: No such file `(/.\S+?)'$} or m{make.*: \*\*\* No rule to make target `(/\S+)'} or m{g(?:cc|\+\+): (/\S+): No such file or directory$} or m{env: (/\S+): No such file or directory$} ) { my $file = $1; warn "Looking for file $file\n"; poldek_file( $file ); } if ( m{^ValueError: Couldn't find include '(.*\.gir)'} ) { my $file = $1; warn "Looking for gir file $file\n"; poldek_file( "/usr/share/gir-1.0/" . $file ); } if ( m{^error: Package `(\S+)' not found in specified Vala API directories or GObject-Introspection GIR directories} ) { my $file = $1; warn "Looking for gir file $file\n"; poldek_file( "/usr/share/vala/vapi/$file.vapi"); } if ( m{failed.*http://www\.oasis-open\.org/docbook/xml/([\d\.]+/\S+\.dtd)} ) { my $dtd = $1; warn "Looking for docbook file $dtd\n"; poldek_file( "/usr/share/sgml/docbook/xml-dtd-$dtd" ); } if ( m{http://docbook.sourceforge.net/release/xsl/current/(\S+\.xsl)} ) { my $db = $1; next if m{^\s*(/usr/bin/)?xsltproc }; warn "Looking for docbook file $db\n"; poldek_file( "/usr/share/sgml/*/$db" ); } if (m{Could not find HTML docbook.xsl}) { add_br("docbook-style-xsl"); } if ( m{LaTeX Error: File `(\S+)' not found} ) { my $tex = $1; warn "Looking for tex file $tex\n"; poldek_file( "/usr/share/tex*/$tex" ); } if ( m{mv: cannot move `\S+' to `(/var/lib/texmf.*?)':} ) { my $tex = $1; warn "Looking for tex file $tex\n"; poldek_file( $tex ); } if ( m{configure: error: C\+\+ preprocessor "/lib/cpp" fails sanity check} ) { add_br( "gcc-c++", "try: %undefine\t__cxx" ); } if ( m{configure: error: C\+\+ compiler cannot create executables} ) { add_br( "libstdc++-devel", "maybe try: %undefine\t__cxx" ); } if ( m{ccache: error: Could not find compiler ".+-g\+\+" in PATH}) { add_br("libstdc++-devel"); } if ( m{configure: error: XML::Parser perl module is required for intltool} ) { add_br( "perl-XML-Parser" ); } if ( m{iconv: conversion from `\S+' is not supported} ) { add_br( "iconv" ); } if (m{rst2man \(python-docutils\) is required to build man pages}) { add_br("docutils"); } if ( m{ (\S+) does not appear in AM_CONDITIONAL$} ) { my $macro = $1; warn "Looking for autotools macro $macro\n"; if ( my $br = $ac2br{ $macro } ) { add_br( $br ); next; } else { $check_ac = 1; } } if ( m{configure\[\d+\]: syntax error: } or m{\./configure\[\d+\]: \S+_\S+: not found} or m{./configure\[\d+\]: .*unexpected} or m{does not appear in AM_CONDITIONAL$} ) { warn "Need to check configure source: $reason\n"; $check_ac = 1; } if ( m{^configure: error:} ) { $check_config_log = 1 unless defined $check_config_log; } if ( m{Check the mkmf.log file for more details} ) { $check_mkmf_log = 1 unless defined $check_mkmf_log; } if ( m{^CMake (?:Error|Warning) at (?:\S+/)?(\S+?)\.cmake:(\d+) } ) { my ( $module, $line ) = ( lc $1, $2 ); my $br; if ( $module eq "findqt4" ) { my $l = $lines[0]; chomp $l; if ( $l =~ /qmake/ ) { add_br( "qt4-qmake", $l ); } elsif ( $l =~ /rcc/ ) { add_br( "qt4-build", $l ); } elsif ( $l =~ /Could NOT find (Qt\S+) header/ ) { add_br( "$1-devel", $l ); } else { warn "unrecognized Qt requirement: $l\n"; } } elsif ( $module eq "qt5coreconfig" ) { my $l = $lines[2]; chomp $l; if ( $l =~ /qmake/ ) { add_br( "qt5-qmake", $l ); } elsif ( $l =~ /moc/) { add_br( "qt5-build", $l ); } else { warn "Unrecognized Qt requirement: $l\n"; } } elsif ( $module eq "cmakedeterminecxxcompiler" ) { add_br( "libstdc++-devel", '"try: -DCMAKE_CXX_COMPILER_WORKS=1 -DCMAKE_CXX_COMPILER="%{__cc}"' ); } elsif ( $br = $cmake2br{ $module . ":" . $line } ) { add_br( $br ); } elsif ( $br = $cmake2br{ $module } ) { add_br( $br ); } elsif ( $br = guess_package( $module =~ /find(.+)/ ) ) { add_br( $br ); } else { $cmake_get_call = 1; warn "Unrecognized cmake error: $reason\n"; } } if ( m{^CMake Error at (?:\S+/)?(\S+?):(\d+) \(find_package\):} ) { my ($file, $line) = (lc $1, $2); #CMake Error at CMakeLists.txt:29 (find_package): # Could not find a package configuration file provided by "LLVM" with any of the following names: # # LLVMConfig.cmake # llvm-config.cmake my @files = grep { /\.cmake$/ } @lines; my @find = map { chomp; s/^\s+//; "/usr/*/cmake/*/$_" } @files; poldek_file(@find); } if ( m{^\s*ERROR: (?:\S+/)?(\S+?\.cmake) not found} ) { my $cmake = $1; warn "Looking for cmake file: $cmake\n"; poldek_file( "/usr/*/cmake/*/$cmake" ) } if ( $cmake_get_call ) { if ( m{^\s*/\S+/(\S+)\.cmake:(\d+) } ) { my ( $module, $line ) = ( lc $1, $2 ); my $br; if ( $br = $cmake2br{ $module . ":" . $line } ) { add_br( $br ); $cmake_get_call = 0; } elsif ( $br = $cmake2br{ $module } ) { add_br( $br ); $cmake_get_call = 0; } elsif ( $br = guess_package( $module =~ /find(.+)/ ) ) { add_br( $br ); $cmake_get_call = 0; } } elsif (m{Can not use "(.+)" module which has not yet been found} or m{Could not find a package configuration file provided by "(.+)"}) { my $cmake = $1; warn "Looking for cmake file: $cmake\n"; poldek_file( "/usr/*/cmake/*/$cmake.cmake", "/usr/*/cmake/*/${cmake}Config.cmake", "/usr/*/cmake/*/${cmake}-config.cmake", ); $cmake_get_call = 0; } } if ( m{^-- WARNING: you are using the obsolete 'PKGCONFIG' macro} ) { add_br( "pkgconfig" ); } if ( m{QT_(QT\S+)_LIBRARY \(ADVANCED\)} or m{X11_(\S+)_LIB \(ADVANCED\)} or m{Qt (\S+) library not found} ) { my $find = $1; my $pkg = guess_package( $find ); if ( $pkg ) { add_br( $pkg ); } else { warn "Cannot quess qt package: $find\n"; } } if ( m{Unable to find a javac compiler;$} ) { add_br( "jdk" ); } if ( m{Could not find (\S+) Java extension for this JVM$} ) { my $jar = $1; warn "Looking for jar file: $jar\n"; poldek_file( "/usr/share/java/$jar.jar", "*/$jar.jar" ) } if ( m{^-- The following OPTIONAL packages could NOT be located on your system} ) { $cmake_pkg_list = 1; } if ( $cmake_pkg_list ) { if ( /\s+\* (\S+) / ) { my $find = $1; my $pkg = guess_package( $find ); if ( $pkg ) { add_br( $pkg ); } else { warn "Cannot quess optional package: $find\n"; } } elsif ( /^\s*$/ ) { $cmake_pkg_list = 0; } } if (m{By not providing "Find(.+)\.cmake" in CMAKE_MODULE_PATH} or m{Could not find a package configuration file provided by "(.+)" }) { my $cmake = $1; my $lcmake = lc $cmake; warn "Looking for cmake file: $cmake\n"; poldek_file( "/usr/*/cmake/*/$cmake.cmake", "/usr/*/cmake/*/${cmake}Config.cmake", "/usr/*/cmake/*/${cmake}-config.cmake", "/usr/*/cmake/*/${lcmake}Config.cmake", "/usr/*/cmake/*/${lcmake}-config.cmake", ) } if ( m{^configure:\d+: checking for (?:"(\S+)"|(\S+))$} ) { my $exec = $1 || $2; if ( @lines and $lines[0] =~ m{^configure:\d+: result: no$} ) { next if $skip{ $exec }; warn "Looking for executable $exec\n"; poldek_file( $exec ) if $exec =~ m#^/#; poldek_file( "/usr/bin/$exec", "/bin/$exec" ); } } if ( m{Could not find (\S+)} ) { my $exec = $1; poldek_file("/usr/bin/$exec", "/bin/$exec" ); } } sub find_configure { return unless /^configure(\.(?:ac|in|in\.in))?$/; return unless -r; warn "$File::Find::name\n"; open F_IN, "<", $_; my $file = $_; while ( ) { chomp; if ( m{^\s*([A-Za-z0-9_]+)\s*(\(.*)?$} ) { my $def = $1; if ( my $br = $ac2br{ $def } ) { add_br( $br, "$file: $_" ); } elsif ( $def !~ /^A[CM]_/ and $def =~ /^_?[A-Z]+_[A-Z_0-9a-z]+$/ ) { #warn "Possible macro unrecognized: $def [[[$_]]]\n"; } } } close F_IN; } use File::Find; if ( $check_ac ) { find( \&find_configure, $builddir ); } sub find_config_log { return unless /^config\.log$/; return unless -r; warn "$File::Find::name\n"; open F_IN, "<", $_; push @lines, ; close F_IN; } if ( $check_config_log ) { $check_config_log = 0; find( \&find_config_log, $builddir ); goto start_check if @lines; } if ($check_mkmf_log) { $check_mkmf_log = 0; find(sub { return unless /^mkmf\.log$/; return unless -r; warn "$File::Find::name\n"; open F_IN, "<", $_; push @lines, ; close F_IN; }, $builddir); goto start_check if @lines; } foreach my $pkg ( sort keys %out ) { print "$pkg -- $out{$pkg}\n"; } # vim: ts=4 sw=4