support/package-builder/SpecParser.py
2820c61a
 import re
0f1fdc4b
 import platform
2820c61a
 from StringUtils import StringUtils
 from SpecStructures import *
45c9260c
 from constants import constants
2820c61a
 
 class SpecParser(object):
343d89e8
 
2820c61a
     def __init__(self):
         self.cleanMacro=rpmMacro().setName("clean")
         self.prepMacro=rpmMacro().setName("prep")
         self.buildMacro=rpmMacro().setName("build")
         self.installMacro=rpmMacro().setName("install")
         self.changelogMacro=rpmMacro().setName("changelog")
         self.checkMacro=rpmMacro().setName("check")
         self.packages={}
         self.specAdditionalContent=""
cb4e8710
         self.globalSecurityHardening=""
fba234bc
         self.defs={}
5f40784b
         self.conditionalCheckMacroEnabled = False
343d89e8
         self.macro_pattern = re.compile(r'%{(\S+?)\}')
5062126c
 
 
2820c61a
     def readPkgNameFromPackageMacro(self,data,basePkgName=None):
         data=" ".join(data.split())
         pkgHeaderName=data.split(" ")
         lenpkgHeaderName = len(pkgHeaderName)
adf248d5
         i=1;
         pkgName = None
         while i<lenpkgHeaderName:
             if pkgHeaderName[i] == "-n" and i+1 < lenpkgHeaderName:
                 pkgName = pkgHeaderName[i+1]
                 break
             if pkgHeaderName[i].startswith('-'):
                 i = i + 2
             else:
                 pkgName = basePkgName+"-"+pkgHeaderName[i]
                 break
         if pkgName is None:
2820c61a
             return True, basePkgName
adf248d5
         return True, pkgName
5062126c
 
343d89e8
     def replaceMacros(self, string):
         """Replace all macros in given string with corresponding values.
 
         For example: a string '%{name}-%{version}.tar.gz' will be transformed to 'foo-2.0.tar.gz'.
 
         :return A string where all macros in given input are substituted as good as possible.
 
         """
         def _is_conditional(macro):
             return macro.startswith("?") or macro.startswith("!")
 
         def _test_conditional(macro):
             if macro[0] == "?":
                 return True
             if macro[0] == "!":
                 return False
             raise Exception("Given string is not a conditional macro")
 
         def _is_macro_defined(macro):
             return (macro in self.defs.keys()) or (macro in constants.userDefinedMacros.keys())
 
         def _get_macro(macro):
             if macro in self.defs.keys():
                 return self.defs[macro]
             elif macro in constants.userDefinedMacros.keys():
                 return constants.userDefinedMacros[macro]
             raise Exception("Unknown macro: " + macro)
 
         def _macro_repl(match):
             macro_name = match.group(1)
             if _is_conditional(macro_name):
                 parts = macro_name[1:].split(":")
                 assert len(parts) > 0
                 if _test_conditional(macro_name):  # ?
                     if _is_macro_defined(parts[0]):
                         if len(parts) == 2:
                             return parts[1]
                         else:
                             return _get_macro(parts[0])
                     else:
                         return ""
                 else:  # !
                     if not _is_macro_defined(parts[0]):
                         if len(parts) == 2:
                             return parts[1]
                     return ""
 
             if _is_macro_defined(macro_name):
                 return _get_macro(macro_name)
             return match.string[match.start():match.end()]
 
         #User macros
         for macroName in constants.userDefinedMacros.keys():
             value = constants.userDefinedMacros[macroName]
             macro="%"+macroName
             if string.find(macro) != -1:
                 string = string.replace(macro,value)
         #Spec definitions
         for macroName in self.defs.keys():
             value = self.defs[macroName]
             macro="%"+macroName
             if string.find(macro) != -1:
                 string = string.replace(macro,value)
         return re.sub(self.macro_pattern, _macro_repl, string)
 
2820c61a
     def parseSpecFile(self,specfile):
         self.createDefaultPackage()
         currentPkg="default"
         specFile = open(specfile)
         lines = specFile.readlines()
         totalLines=len(lines)
         i=0
         while i < totalLines:
             line = lines[i].strip()
0f1fdc4b
             if self.isConditionalArch(line):
                 if (platform.machine() != self.readConditionalArch(line)):
                     # skip conditional body
                     deep = 1
                     while (i < totalLines and deep != 0):
                         i=i+1
                         line = lines[i].strip()
                         if self.isConditionalMacroStart(line):
                             deep = deep + 1
                         elif self.isConditionalMacroEnd(line):
                             deep = deep - 1
343d89e8
             elif self.isIfCondition(line):
                 if (not self.isConditionTrue(line)):
                     # skip conditional body
                     deep = 1
                     while (i < totalLines and deep != 0):
                         i=i+1
                         line = lines[i].strip()
                         if self.isConditionalMacroStart(line):
                             deep = deep + 1
                         elif self.isConditionalMacroEnd(line):
                             deep = deep - 1
0f1fdc4b
             elif self.isSpecMacro(line):
2820c61a
                 macro,i=self.readMacroFromFile(i, lines)
                 self.updateMacro(macro)
             elif self.isPackageMacro(line):
                 defaultpkg = self.packages.get('default')
                 returnVal,packageName=self.readPkgNameFromPackageMacro(line, defaultpkg.name)
343d89e8
                 packageName=self.replaceMacros(packageName)
2820c61a
                 if not returnVal:
                     return False
                 if re.search('^'+'%package',line) :
                     pkg = Package(defaultpkg)
                     pkg.name=packageName
                     currentPkg=packageName
                     self.packages[pkg.name]=pkg
                 else:
                     if defaultpkg.name == packageName :
                         packageName = 'default'
                     macro,i=self.readMacroFromFile(i, lines)
adf248d5
                     if not self.packages.has_key(packageName):
                         i=i+1
                         continue
2820c61a
                     self.packages[packageName].updatePackageMacro(macro)
             elif self.isPackageHeaders(line):
                 self.readPackageHeaders(line, self.packages[currentPkg])
cb4e8710
             elif self.isGlobalSecurityHardening(line):
                 self.readSecurityHardening(line)
3cc43c92
             elif self.isChecksum(line):
                 self.readChecksum(line, self.packages[currentPkg])
fba234bc
             elif self.isDefinition(line):
                 self.readDefinition(line)
5f40784b
             elif self.isConditionalCheckMacro(line):
                 self.conditionalCheckMacroEnabled = True
0f1fdc4b
             elif self.conditionalCheckMacroEnabled and self.isConditionalMacroEnd(line):
5f40784b
                 self.conditionalCheckMacroEnabled = False
2820c61a
             else:
                 self.specAdditionalContent+=line+"\n"
             i=i+1
         specFile.close()
5062126c
 
2820c61a
     def createDefaultPackage(self):
         pkg = Package()
         self.packages["default"]=pkg
5062126c
 
2820c61a
     def readMacroFromFile(self,currentPos,lines):
         macro = rpmMacro()
         line = lines[currentPos]
         macro.position = currentPos
         macro.endposition=currentPos
         endPos=len(lines)
         line = " ".join(line.split())
         flagindex = line.find(" ")
         if flagindex != -1:
             macro.macroFlag=line[flagindex+1:]
             macro.macroName=line[:flagindex]
         else:
             macro.macroName=line
 
         if currentPos+1 < len(lines) and self.isMacro(lines[currentPos+1]):
             return macro,currentPos
5062126c
 
2820c61a
         for j in range(currentPos+1,endPos):
             content = lines[j]
             if j+1 < endPos and self.isMacro(lines[j+1]):
                 return macro,j
             macro.content += content +'\n'
             macro.endposition=j
         return macro,endPos
5062126c
 
2820c61a
 
     def updateMacro(self,macro):
         if macro.macroName == "%clean":
             self.cleanMacro=macro
             return True
         if macro.macroName == "%prep":
             self.prepMacro=macro
             return True
         if macro.macroName == "%build":
             self.buildMacro=macro
             return True
         if macro.macroName == "%install":
             self.installMacro=macro
             return True
         if macro.macroName == "%changelog":
             self.changelogMacro=macro
             return True
         if macro.macroName == "%check":
             self.checkMacro=macro
             return True
         return False
5062126c
 
2820c61a
     def isMacro(self,line):
0f1fdc4b
         return self.isPackageMacro(line) or self.isSpecMacro(line) or self.isConditionalMacroStart(line) or self.isConditionalMacroEnd(line)
 
     def isConditionalArch(self,line):
         if re.search('^'+'%ifarch',line) :
             return True
         return False
5062126c
 
2820c61a
     def isSpecMacro(self,line):
         if re.search('^'+'%clean',line) :
             return True
         elif re.search('^'+'%prep',line) :
5062126c
             return True
2820c61a
         elif re.search('^'+'%build',line) :
             return True
         elif re.search('^'+'%install',line) :
             return True
         elif re.search('^'+'%changelog',line) :
             return True
         elif re.search('^'+'%check',line) :
             return True
         return False
5062126c
 
2820c61a
     def isPackageMacro(self,line):
         line=line.strip()
 
         if re.search('^'+'%post',line) :
             return True
         elif re.search('^'+'%postun',line) :
             return True
         elif re.search('^'+'%files',line) :
             return True
         elif re.search('^'+'%description',line) :
             return True
         elif re.search('^'+'%package',line) :
             return True
         return False
5062126c
 
2820c61a
     def isPackageHeaders(self,line):
         if re.search('^'+'summary:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'name:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'group:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'license:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'version:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'release:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'distribution:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'requires:',line,flags=re.IGNORECASE) :
             return True
45c9260c
         elif re.search('^'+'requires\((pre|post|preun|postun)\):',line,flags=re.IGNORECASE) :
             return True
2820c61a
         elif re.search('^'+'provides:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'obsoletes:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'conflicts:',line,flags=re.IGNORECASE) :
             return True
920a9773
         elif re.search('^'+'url:',line,flags=re.IGNORECASE) :
             return True
2820c61a
         elif re.search('^'+'source[0-9]*:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'patch[0-9]*:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'buildrequires:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'buildprovides:',line,flags=re.IGNORECASE) :
             return True
         elif re.search('^'+'buildarch:',line,flags=re.IGNORECASE) :
             return True
         return False
 
cb4e8710
     def isGlobalSecurityHardening(self,line):
         if re.search('^%global *security_hardening',line,flags=re.IGNORECASE) :
             return True
         return False
 
3cc43c92
     def isChecksum(self,line):
         if re.search('^%define *sha1',line,flags=re.IGNORECASE) :
             return True
         return False
 
fba234bc
     def isDefinition(self,line):
         if re.search('^'+'%define',line) :
             return True
         if re.search('^'+'%global',line) :
             return True
         return False
 
0f1fdc4b
     def readConditionalArch(self,line):
         w=line.split()
         if len(w) == 2:
            return w[1]
         return None
 
fba234bc
     def readDefinition(self,line):
         listDefines=line.split()
         if len(listDefines) == 3:
            self.defs[listDefines[1]] = listDefines[2]
            return True
         return False
 
2820c61a
     def readHeader(self,line):
         headerSplitIndex=line.find(":")
         if(headerSplitIndex+1 == len(line) ):
             print line
             print "Error:Invalid header"
             return False, None,None
         headerName=line[0:headerSplitIndex].lower()
         headerContent=line[headerSplitIndex+1:].strip()
         return True,headerName,headerContent
 
 
     def readDependentPackageData(self,line):
         strUtils = StringUtils()
         listPackages=line.split(",")
         listdependentpkgs=[]
         for line in listPackages:
45c9260c
             line=strUtils.getStringInConditionalBrackets(line)
2820c61a
             listContents=line.split()
             totalContents = len(listContents)
             i=0
             while i < totalContents:
                 dpkg = dependentPackageData()
                 compare=None
45c9260c
                 packageName=listContents[i]
f5cac196
                 if listContents[i].startswith("/"):
45c9260c
                     provider=constants.providedBy.get(listContents[i], None)
f5cac196
                     i=i+1
45c9260c
                     if provider is not None:
                         packageName=provider
                     else:
                         continue
2820c61a
                 if i+2 < len(listContents):
                     if listContents[i+1] == ">=":
                         compare="gte"
                     elif listContents[i+1] == "<=":
                         compare="lte"
                     elif listContents[i+1] == "==":
                         compare="eq"
                     elif listContents[i+1] == "<":
                         compare="lt"
                     elif listContents[i+1] == ">":
                         compare="gt"
                     elif listContents[i+1] == "=":
                         compare="eq"
5062126c
 
2820c61a
                 if compare is not None:
45c9260c
                     dpkg.package=packageName
2820c61a
                     dpkg.compare=compare
                     dpkg.version=listContents[i+2]
                     i=i+3
                 else:
45c9260c
                     dpkg.package=packageName
2820c61a
                     i=i+1
                 listdependentpkgs.append(dpkg)
         return listdependentpkgs
 
     def readPackageHeaders(self,line,pkg):
         returnVal,headerName,headerContent=self.readHeader(line)
         if not returnVal:
             return False
 
343d89e8
         headerContent=self.replaceMacros(headerContent)
2820c61a
         if headerName == 'summary':
             pkg.summary=headerContent
             return True
         if headerName == 'name':
             pkg.name=headerContent
343d89e8
             if (pkg == self.packages["default"]):
                 self.defs["name"] = pkg.name
2820c61a
             return True
         if headerName == 'group':
             pkg.group=headerContent
             return True
         if headerName == 'license':
             pkg.license=headerContent
             return True
         if headerName == 'version':
             pkg.version=headerContent
343d89e8
             if (pkg == self.packages["default"]):
                 self.defs["version"] = pkg.version
2820c61a
             return True
         if headerName == 'buildarch':
             pkg.buildarch=headerContent
             return True
         if headerName == 'release':
             pkg.release=headerContent
343d89e8
             if (pkg == self.packages["default"]):
                 self.defs["release"] = pkg.release
2820c61a
             return True
         if headerName == 'distribution':
             pkg.distribution=headerContent
             return True
920a9773
         if headerName == 'url':
             pkg.URL=headerContent
             return True
2820c61a
         if headerName.find('source') != -1:
             pkg.sources.append(headerContent)
             return True
         if headerName.find('patch') != -1:
             pkg.patches.append(headerContent)
             return True
45c9260c
         if headerName.startswith('requires') or headerName == 'provides' or headerName == 'obsoletes' or headerName == 'conflicts' or headerName == 'buildrequires' or headerName == 'buildprovides':
2820c61a
             dpkg=self.readDependentPackageData(headerContent)
             if dpkg is None:
                 return False
45c9260c
             if headerName.startswith('requires'):
2820c61a
                 pkg.requires.extend(dpkg)
             if headerName == 'provides':
                 pkg.provides.extend(dpkg)
             if headerName == 'obsoletes':
                 pkg.obsoletes.extend(dpkg)
             if headerName == 'conflicts':
                 pkg.conflicts.extend(dpkg)
             if headerName == 'buildrequires':
5f40784b
                 if self.conditionalCheckMacroEnabled:
                     pkg.checkbuildrequires.extend(dpkg)
                 else:
                     pkg.buildrequires.extend(dpkg)
2820c61a
             if headerName == 'buildprovides':
                 pkg.buildprovides.extend(dpkg)
5062126c
 
2820c61a
             return True
         return False
cb4e8710
 
     def readSecurityHardening(self,line):
         data = line.lower().strip();
         words=data.split(" ")
         nrWords = len(words)
         if (nrWords != 3):
             print "Error: Unable to parse line: "+line
             return False
94c12193
         if (words[2] != "none" and words[2] != "nonow" and words[2] != "nopie") :
cb4e8710
             print "Error: Invalid security_hardening value: " + words[2]
             return False
         self.globalSecurityHardening = words[2]
         return True;
3cc43c92
 
     def readChecksum(self,line,pkg):
         strUtils = StringUtils()
343d89e8
         line=self.replaceMacros(line)
3cc43c92
         data = line.strip();
ec1351c5
         words=data.split()
3cc43c92
         nrWords = len(words)
         if (nrWords != 3):
             print "Error: Unable to parse line: "+line
             return False
         value=words[2].split("=")
         if (len(value) != 2):
             print "Error: Unable to parse line: "+line
             return False
         matchedSources=[]
         for source in pkg.sources:
             sourceName=strUtils.getFileNameFromURL(source)
             if (sourceName.startswith(value[0])):
                 matchedSources.append(sourceName)
         if (len(matchedSources) == 0):
             print "Error: Can not find match for sha1 "+value[0]
             return False
         if (len(matchedSources) > 1):
bc165f64
             print "Error: Too many matched Sources:" + ' '.join(matchedSources) + " for sha1 "+value[0]
3cc43c92
             return False
         pkg.checksums[sourceName] = value[1]
         return True;
5f40784b
 
     def isConditionalCheckMacro(self,line):
         data = line.strip()
         words = data.split()
         nrWords = len(words)
         if(nrWords != 2):
             return False
         if(words[0] != "%if" or words[1] != "%{with_check}"):
             return False
         return True
 
343d89e8
     def isIfCondition(self,line):
         return line.startswith("%if ")
 
     # Supports only %if %{}
     def isConditionTrue(self,line):
         data = line.strip()
         words = data.split()
         nrWords = len(words)
         # condition like %if a > b is not supported
         if(nrWords != 2):
             return True
         if (self.replaceMacros(words[1]) == "0"):
             return False
         return True
 
0f1fdc4b
     def isConditionalMacroStart(self,line):
         return line.startswith("%if")
 
     def isConditionalMacroEnd(self,line):
         return (line.strip() == "%endif")