#############################################################################
# File		: SpecCheck.py
# Package	: rpmlint
# Author	: Frederic Lepied
# Created on	: Thu Oct  7 17:06:14 1999
# Version	: $Id: SpecCheck.py,v 1.25 2003/12/22 11:27:21 flepied Exp $
# Purpose	: check the spec file of a source rpm.
#############################################################################

from Filter import *
import AbstractCheck
import re
import sys
import rpm
import string
import Config

# Don't check for hardcoded library paths in biarch packages
DEFAULT_BIARCH_PACKAGES='^(gcc|glibc)'

# Don't check for hardcoded library paths in packages which can have
# their noarch files in /usr/lib/<package>/*, or packages that can't
# be installed on biarch systems
DEFAULT_HARDCODED_LIB_PATH_EXCEPTIONS='/lib/(modules|cpp|perl5|rpm|hotplug)($|[\s/,])'

spec_regex=re.compile(".spec$")
patch_regex=re.compile("^\s*Patch(.*?)\s*:\s*([^\s]+)")
applied_patch_regex=re.compile("^\s*%patch.*-P\s*([^\s]*)|^\s*%patch([^\s]*)\s")
source_dir_regex=re.compile("^[^#]*(\$RPM_SOURCE_DIR|%{?_sourcedir}?)")
obsolete_tags_regex=re.compile("^(Copyright|Serial)\s*:\s*([^\s]+)")
buildroot_regex=re.compile('Buildroot\s*:\s*([^\s]+)', re.IGNORECASE)
tmp_regex=re.compile('^/')
clean_regex=re.compile('^%clean')
changelog_regex=re.compile('^%changelog')
configure_start_regex=re.compile('\./configure')
configure_libdir_spec_regex=re.compile('\./configure[^#]*--libdir=([^\s]+)[^#]*')
lib_package_regex=re.compile('^%package.*\Wlib')
mklibname_regex=re.compile('%mklibname')
ifarch_regex=re.compile('%ifn?arch')
if_regex=re.compile('%if\s+')
endif_regex=re.compile('%endif')
biarch_package_regex=re.compile(DEFAULT_BIARCH_PACKAGES)
hardcoded_lib_path_exceptions_regex=re.compile(Config.getOption('HardcodedLibPathExceptions', DEFAULT_HARDCODED_LIB_PATH_EXCEPTIONS))

# Only check for /lib, /usr/lib, /usr/X11R6/lib
# TODO: better handling of X libraries and modules.
hardcoded_library_paths='(/lib|/usr/lib|/usr/X11R6/lib/(?!([^/]+/)+)[^/]*\\.([oa]|la|so[0-9.]*))'
hardcoded_library_path_regex=re.compile('^[^#]*((^|\s+|\.\./\.\.|\${?RPM_BUILD_ROOT}?|%{?buildroot}?|%{?_prefix}?)' + hardcoded_library_paths + '(?=[\s;/])([^\s,;]*))')

def file2string(file):
    fd=open(file, "r")
    content=fd.readlines()
    fd.close()
    return content
    
class SpecCheck(AbstractCheck.AbstractCheck):
    
    def __init__(self):
	AbstractCheck.AbstractCheck.__init__(self, "SpecCheck")

    def check(self, pkg):
        if not pkg.isSource():
            return

        # lookup spec file
        files=pkg.files()
	spec_file=None
	for f in files.keys():
	    if spec_regex.search(f):
                spec_file=pkg.dirName() + "/" + f
                break
        if not spec_file:
            printError(pkg, "no-spec-file")
        else:
            if f != pkg[rpm.RPMTAG_NAME] + ".spec":
                printError(pkg, "invalid-spec-name", f)
                
            # check content of spec file
            spec=file2string(spec_file)
            patches={}
            applied_patches=[]
            applied_patches_ifarch=[]
            source_dir=None
            buildroot=0
            clean=0
            changelog=0
            configure=0
            configure_cmdline=""
            mklibname=0
            lib=0
            if_depth=0
            ifarch_depth=-1
            
            # gather info from spec lines
            for line in spec:

                # I assume that the changelog section is at the end of the spec
                # to avoid wrong warnings
                res=changelog_regex.search(line)
                if res:
                    changelog=1
                    break

                res=ifarch_regex.search(line)
                if res:
                    if_depth = if_depth + 1
                    ifarch_depth = if_depth
                res=if_regex.search(line)
                if res:
                    if_depth = if_depth + 1
                res=endif_regex.search(line)
                if res:
                    if ifarch_depth == if_depth:
                        ifarch_depth = -1
                    if_depth = if_depth - 1
                
                res=patch_regex.search(line)
                if res:
                    patches[res.group(1)]=res.group(2)
                else:
                    res=applied_patch_regex.search(line)
                    if res:
                        applied_patches.append(res.group(1) or res.group(2))
                        if ifarch_depth > 0:
                            applied_patches_ifarch.append(res.group(1))
                    elif not source_dir:
                        res=source_dir_regex.search(line)
                        if res:
                            source_dir=1
                            printError(pkg, "use-of-RPM_SOURCE_DIR")
                            
                res=obsolete_tags_regex.search(line)
                if res:
                    printWarning(pkg, "obsolete-tag", res.group(1))
				
                if configure:
                    if configure_cmdline[-1] == "\\":
                        configure_cmdline=configure_cmdline[:-1] + string.strip(line)
                    else:
                        configure=0
                        res=configure_libdir_spec_regex.search(configure_cmdline)
                        if not res:
                            printError(pkg, "configure-without-libdir-spec")
                        else:
                            res=re.match(hardcoded_library_paths, res.group(1))
                            if res:
                                printError(pkg, "hardcoded-library-path", res.group(1), "in configure options")
                
                res=configure_start_regex.search(line)
                if not changelog and res:
                    configure=1
                    configure_cmdline=string.strip(line)
                
                res=hardcoded_library_path_regex.search(line)
                if not changelog and res and not (biarch_package_regex.match(pkg[rpm.RPMTAG_NAME]) or hardcoded_lib_path_exceptions_regex.search(string.lstrip(res.group(1)))):
                    printError(pkg, "hardcoded-library-path", "in", string.lstrip(res.group(1)))
                
                res=buildroot_regex.search(line)
                if res:
                    buildroot=1
                    if tmp_regex.search(res.group(1)):
                        printWarning(pkg, 'hardcoded-path-in-buildroot-tag', res.group(1))

                if not clean and clean_regex.search(line):
                    clean=1

                if mklibname_regex.search(line):
                    mklibname=1

                if lib_package_regex.search(line):
                    lib=1
                    
            if not buildroot:
                printError(pkg, 'no-buildroot-tag')

            if not clean:
                printError(pkg, 'no-%clean-section')

            if lib and not mklibname:
                printError(pkg, 'lib-package-without-%mklibname')
                
            # process gathered info
            for p in patches.keys():
                if p in applied_patches_ifarch:
                    printWarning(pkg, "%ifarch-applied-patch", "Patch" + p + ":", patches[p])
                if p not in applied_patches:
                    if p == "" and "0" in applied_patches:
                        continue
                    if p == "0" and "" in applied_patches:
                        continue
                    printWarning(pkg, "patch-not-applied", "Patch" + p + ":", patches[p])

# Create an object to enable the auto registration of the test
check=SpecCheck()

# Add information about checks
if Config.info:
    addDetails(
'no-spec-file',
'''No spec file was specified in your RPM building. Please specify a valid
SPEC file to build a valid RPM package.''',

'invalid-spec-name',
'''Your spec file must finish with '.spec'. If it's not the case, rename your
file and rebuild your package.''',

'use-of-RPM_SOURCE_DIR',
'''You use RPM_SOURCE_DIR in your spec file. If you have to use a directory
for building, use RPM_BUILD_ROOT instead.''',

'patch-not-applied',
'''A patch is included in your package but was not applied. Refer to the patches
documentation to see what's wrong.''',

'obsolete-tag',
'''The following tags are obsolete: Copyright and Serial. They must
be replaced by License and Epoch respectively.''',

'no-buildroot-tag',
'''The BuildRoot tag isn't used in your spec. It must be used to
allow build as non root.''',

'hardcoded-path-in-buildroot-tag',
'''A path is hardcoded in your Buildroot tag. It should be replaced
by something like %{_tmppath}/%name-root.''',

'hardcoded-library-path',
'''A library path is hardcoded to one of the following paths: /lib,
/usr/lib. It should be replaced by something like /%{_lib} or %{_libdir}.''',

'configure-without-libdir-spec',
'''A configure script is run without specifying the libdir. Configure
options must be augmented with something like libdir=%{_libdir}.''',

'no-%clean-section',
'''The spec file doesn't contain a %clean section to remove the files installed
by the %install section.''',

'lib-package-without-%mklibname',
'''The package name must be built using %mklibname to allow lib64 and lib32
coexistence.''',

'%ifarch-applied-patch',
'''A patch is applied inside an %ifarch block. Patches must be applied
on all architectures and may contain necessary configure and/or code
patch to be effective only on a given arch.'''

)

# SpecCheck.py ends here
