3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 """For handling rpm related work"""
20 from rpmUtils.miscutils import splitFilename
22 from rpmbuilder.baseerror import RpmbuilderError
23 from rpmbuilder.executor import Executor
26 class Specworker(object):
27 """ Working with spec files """
29 def __init__(self, directory, specfile=None):
30 self.logger = logging.getLogger(__name__)
32 if self.__verify_specfile_exists(os.path.join(directory, specfile)):
33 self.specfilename = specfile
35 self.logger.critical("Specfile %s not found", specfile)
36 raise SpecError("Spec file not found")
38 self.specfilename = self.__locate_spec_file(directory)
40 self.specfilefullpath = os.path.join(directory, self.specfilename)
45 self.source_files = []
46 self.source_file_extension = None
48 self.buildrequires = []
52 self.spec_globals = {}
56 return 'name:%s version:%s' % (self.name, self.version)
58 def __getattr__(self, item):
59 return self.spec_globals.get(item)
62 def __locate_spec_file(directory):
63 """ Finding spec files from directory """
64 logger = logging.getLogger(__name__)
65 logger.debug("Searching for spec files under: %s", directory)
68 for occurence in os.listdir(directory):
69 filefullpath = os.path.join(directory, occurence)
70 if os.path.isfile(filefullpath) and filefullpath.endswith(".spec"):
71 logger.info("Found spec file: %s", occurence)
73 logger.critical("Project has more than one spec files."
74 "I don't know which one to use.")
75 raise SpecError("Multiple spec files")
81 raise SpecError("No spec file available")
83 def _read_spec_sources(self):
84 cmd = ['spectool', '-n', '-S', self.specfilefullpath]
85 sources = self._parse_spectool_output(Executor().run(cmd))
86 self.source_file_extension = self.__get_source_file_extension(sources[0])
89 def _read_spec_patches(self):
90 cmd = ['spectool', '-n', '-P', self.specfilefullpath]
91 return self._parse_spectool_output(Executor().run(cmd))
93 def _parse_spectool_output(self, output):
94 return [line.split(':', 1)[1].strip() for line in output.splitlines()]
96 def _get_package_names(self):
97 cmd = ['rpm', '-q', '--qf', '%{NAME}\n', '--specfile', self.specfilefullpath]
98 return Executor().run(cmd).splitlines()
100 def _get_version(self):
101 cmd = ['rpmspec', '-q', '--queryformat', '%{VERSION}\n', self.specfilefullpath]
102 return Executor().run(cmd).splitlines()[0]
105 """ Reading spec file values to variables """
106 self.logger.debug("Reading spec file %s", self.specfilefullpath)
107 self.source_files = self._read_spec_sources()
108 self.patch_files = self._read_spec_patches()
109 self.packages = self._get_package_names()
110 self.name = self.packages[0]
111 self.version = self._get_version()
113 with open(self.specfilefullpath, 'r') as filep:
116 linestripped = line.strip()
118 if linestripped.startswith("#") or not linestripped:
121 if linestripped.lower().startswith("%global"):
123 var, val = re.match(r'^%global (\w+) (.+)$', linestripped).groups()
124 self.spec_globals[var] = val
125 except Exception as err:
126 logger = logging.getLogger(__name__)
128 'Failed to parse %global macro "{}" (error: {})'.format(linestripped,
131 elif linestripped.lower().startswith("buildrequires:") and "only_builddep_resolve" not in self.spec_globals:
132 self.buildrequires.extend(self.__get_value_from_line(linestripped))
134 elif linestripped.lower().startswith("requires:") and "only_builddep_resolve" not in self.spec_globals:
135 self.requires.extend(self.__get_value_from_line(linestripped))
137 elif linestripped.lower().startswith("release:"):
138 templist = self.__get_value_from_line(linestripped)
139 self.release = templist[0]
141 elif linestripped.lower().startswith("name:"):
144 elif linestripped.lower().startswith("%package"):
147 "SPEC file is faulty. Name of the package should be defined before defining subpackages")
149 "Problem in spec file. Subpackages defined before %packages")
151 elif linestripped.lower().startswith("%files"):
153 templist = self.__get_package_names_from_line(self.name, linestripped)
154 self.files.extend(templist)
156 self.logger.critical(
157 "SPEC file is faulty. Name of the package should be defined before defining subpackages")
158 raise SpecError("Problem in spec file. No %files defined")
160 if not self.verify_spec_ok():
161 raise SpecError("Inspect file %s" % self.specfilefullpath)
162 self.logger.info("Reading spec file done: %s", str(self))
164 def verify_spec_ok(self):
165 """ Check that spec file contains the necessary building blocks """
168 self.logger.critical("Spec does not have name defined")
171 self.logger.critical("Spec does not contain version")
174 self.logger.critical("Spec does not contain release")
176 if not self.source_file_extension:
177 self.logger.critical(
178 "Spec does not define source information with understandable archive method")
183 def __get_source_file_extension(line):
184 """ Read source file archive file end """
186 if line.endswith('.tar.gz'):
188 elif line.endswith('.tgz'):
190 elif line.endswith('.tar'):
192 elif line.endswith('.tar.bz2'):
194 elif line.endswith('.tar.xz'):
196 elif line.endswith('.zip'):
200 "Unknown source archive format. Supported are: tar.gz, tgz, tar, tar.bz2, tar.xz, zip")
203 def __get_value_from_line(line):
204 """ Read spec line where values come after double-colon """
206 linewithgroups = re.search('(.*):(.*)$', line)
207 linevalues = linewithgroups.group(2).strip().replace(' ', ',').split(',')
208 for linevalue in linevalues:
209 valuelist.append(linevalue.strip(' \t\n\r'))
213 def __get_package_names_from_line(name, line):
214 """ Read spec line where package names are defined """
215 linewithgroups = re.search('%(.*) (.*)$', line)
217 value = linewithgroups.group(2).strip(' \t\n\r')
218 return [name + '-' + value]
221 def __verify_specfile_exists(self, specfile):
222 """ Check that the given spec file exists """
223 if not specfile.endswith(".spec"):
224 self.logger.error("Given specfile %s does not end with .spec prefix", specfile)
227 if os.path.isfile(specfile):
229 self.logger.error("Could not locate specfile %s", specfile)
233 class Repotool(object):
234 """ Module for handling rpm related functions """
237 self.logger = logging.getLogger(__name__)
239 def createrepo(self, directory):
240 """ Create a yum repository of the given directory """
241 createrepo_executable = "/usr/bin/createrepo"
242 createrepocommand = [createrepo_executable, '--update', directory]
243 outputfile = os.path.join(directory, 'log.txt')
244 with open(outputfile, 'w') as filep:
246 subprocess.check_call(createrepocommand, shell=False, stdout=filep,
247 stderr=subprocess.STDOUT)
248 except subprocess.CalledProcessError:
249 self.logger.critical("There was error running createrepo")
250 raise RepotoolError("There was error running createrepo")
252 self.logger.error(createrepo_executable + "command not available")
253 raise RepotoolError("No createrepo tool available")
255 def latest_release_of_package(self, directory, package, version):
256 """ Return latest release of the given package """
257 self.logger.debug("Looking for latest %s - %s under %s",
258 package, version, directory)
259 latest_found_release = 0
260 if os.path.isdir(directory):
261 for occurence in os.listdir(directory):
262 filefullpath = os.path.join(directory, occurence)
263 if os.path.isfile(filefullpath) \
264 and filefullpath.endswith(".rpm") \
265 and not filefullpath.endswith(".src.rpm"):
266 (rpmname, rpmversion, rpmrelease, _, _) = splitFilename(occurence)
267 if rpmname == package and rpmversion == version:
268 self.logger.debug("Found rpm " + filefullpath)
269 if latest_found_release < rpmrelease:
270 self.logger.debug("Found rpm to match and to be the latest")
271 latest_found_release = rpmrelease
272 if latest_found_release == 0:
273 self.logger.debug("Did not find any previous releases of %s", package)
274 return str(latest_found_release)
276 def next_release_of_package(self, directory, package, version, oldrelease):
277 """ Return next release of the given package """
278 self.logger.debug("Looking for next release number for %s - %s under %s ", package, version,
281 specreleasematch = re.search('^([0-9]+)(.*)$', oldrelease)
282 if specreleasematch and specreleasematch.group(2):
283 releasesuffix = specreleasematch.group(2)
287 latest_release = self.latest_release_of_package(directory, package, version)
288 self.logger.debug("Latest release of the package: " + latest_release)
289 rematches = re.search('^([0-9]+)(.*)$', latest_release)
290 if rematches.group(1).isdigit():
291 nextrelease = str(int(rematches.group(1)) + 1) + releasesuffix
292 self.logger.debug("Next release of the package: " + nextrelease)
295 self.logger.critical("Could not parse release \"%s\" from package \"%s\"",
296 latest_release, package)
297 raise RepotoolError("Could not process release in rpm")
300 class RepotoolError(RpmbuilderError):
301 """ Exceptions originating from repotool """
305 class SpecError(RpmbuilderError):
306 """ Exceptions originating from spec content """