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") |