Refactor upstream patching to strategy.

Will allow projects to swap in more advanced strategies for patching
their upstream projects.

Also renaming SatelliteBuilder to UpstreamBuilder.
This commit is contained in:
Devan Goodwin 2010-05-20 22:05:26 -03:00
parent 784698e46f
commit e0f3e82ea3
5 changed files with 169 additions and 105 deletions

View file

@ -15,7 +15,6 @@ Code for building Spacewalk/Satellite tarballs, srpms, and rpms.
""" """
import os import os
import re
import sys import sys
import commands import commands
@ -23,6 +22,7 @@ from tito.common import (debug, run_command, error_out, find_git_root,
create_tgz, get_build_commit, find_spec_file, get_script_path, create_tgz, get_build_commit, find_spec_file, get_script_path,
get_relative_project_dir, check_tag_exists, get_relative_project_dir, check_tag_exists,
get_commit_count, get_latest_commit) get_commit_count, get_latest_commit)
from tito.common import get_class_by_name
DEFAULT_KOJI_OPTS = "build --nowait" DEFAULT_KOJI_OPTS = "build --nowait"
DEFAULT_CVS_BUILD_DIR = "cvswork" DEFAULT_CVS_BUILD_DIR = "cvswork"
@ -583,6 +583,8 @@ class Builder(object):
commands.getoutput("rm -rf %s" % self.rpmbuild_dir) commands.getoutput("rm -rf %s" % self.rpmbuild_dir)
debug("Cleaning up [%s]" % self.cvs_package_workdir) debug("Cleaning up [%s]" % self.cvs_package_workdir)
run_command("rm -rf %s" % self.cvs_package_workdir) run_command("rm -rf %s" % self.cvs_package_workdir)
else:
print("Leaving rpmbuild files in: %s" % self.rpmbuild_dir)
def _create_build_dirs(self): def _create_build_dirs(self):
""" """
@ -814,11 +816,11 @@ class CvsBuilder(NoTgzBuilder):
return rpms return rpms
class SatelliteBuilder(NoTgzBuilder): class UpstreamBuilder(NoTgzBuilder):
""" """
Builder for packages that are based off some upstream version in Spacewalk Builder for packages that are based off an upstream git tag.
git. Commits applied in Satellite git become patches applied to the Commits applied in downstream git become patches applied to the
upstream Spacewalk tarball. upstream tarball.
i.e. satellite-java-0.4.0-5 built from spacewalk-java-0.4.0-1 and any i.e. satellite-java-0.4.0-5 built from spacewalk-java-0.4.0-1 and any
patches applied in satellite git. patches applied in satellite git.
@ -845,8 +847,6 @@ class SatelliteBuilder(NoTgzBuilder):
# Need to assign these after we've exported a copy of the spec file: # Need to assign these after we've exported a copy of the spec file:
self.upstream_version = None self.upstream_version = None
self.upstream_tag = None self.upstream_tag = None
self.patch_filename = None
self.patch_file = None
# When syncing files with CVS, only copy files with these extensions: # When syncing files with CVS, only copy files with these extensions:
self.cvs_copy_extensions = (".spec", ".patch") self.cvs_copy_extensions = (".spec", ".patch")
@ -899,80 +899,15 @@ class SatelliteBuilder(NoTgzBuilder):
return return
self._generate_patches() self._generate_patches()
self._insert_patches_into_spec_file()
def _generate_patches(self): def _generate_patches(self):
""" """
Generate patches for any differences between our tag and the Generate patches for any differences between our tag and the
upstream tag. upstream tag.
""" """
self.patch_filename = "%s-to-%s-%s.patch" % (self.upstream_tag, from tito.strategy.patcher import DefaultPatcher
self.project_name, self.build_version) patcher = DefaultPatcher(builder=self)
self.patch_file = os.path.join(self.rpmbuild_gitcopy, patcher.run()
self.patch_filename)
os.chdir(os.path.join(self.git_root, self.relative_project_dir))
print("Generating patch [%s]" % self.patch_filename)
debug("Patch: %s" % self.patch_file)
patch_command = "git diff --relative %s..%s > %s" % \
(self.upstream_tag, self.git_commit_id, self.patch_file)
debug("Generating patch with: %s" % patch_command)
output = run_command(patch_command)
print(output)
# Creating two copies of the patch here in the temp build directories
# just out of laziness. Some builders need sources in SOURCES and
# others need them in the git copy. Being lazy here avoids one-off
# hacks and both copies get cleaned up anyhow.
run_command("cp %s %s" % (self.patch_file, self.rpmbuild_sourcedir))
def _insert_patches_into_spec_file(self):
"""
Insert the generated patches into the copy of the spec file we'll be
building with.
"""
f = open(self.spec_file, 'r')
lines = f.readlines()
patch_pattern = re.compile('^Patch(\d+):')
source_pattern = re.compile('^Source\d+:')
# Find the largest PatchX: line, or failing that SourceX:
patch_number = 0 # What number should we use for our PatchX line
patch_insert_index = 0 # Where to insert our PatchX line in the list
patch_apply_index = 0 # Where to insert our %patchX line in the list
array_index = 0 # Current index in the array
for line in lines:
match = source_pattern.match(line)
if match:
patch_insert_index = array_index + 1
match = patch_pattern.match(line)
if match:
patch_insert_index = array_index + 1
patch_number = int(match.group(1)) + 1
if line.startswith("%prep"):
# We'll apply patch right after prep if there's no %setup line
patch_apply_index = array_index + 2
elif line.startswith("%setup"):
patch_apply_index = array_index + 2 # already added a line
array_index += 1
debug("patch_insert_index = %s" % patch_insert_index)
debug("patch_apply_index = %s" % patch_apply_index)
if patch_insert_index == 0 or patch_apply_index == 0:
error_out("Unable to insert PatchX or %patchX lines in spec file")
lines.insert(patch_insert_index, "Patch%s: %s\n" % (patch_number,
self.patch_filename))
lines.insert(patch_apply_index, "%%patch%s -p1\n" % (patch_number))
f.close()
# Now write out the modified lines to the spec file copy:
f = open(self.spec_file, 'w')
for line in lines:
f.write(line)
f.close()
def _get_upstream_version(self): def _get_upstream_version(self):
""" """
@ -1008,3 +943,9 @@ class SatelliteBuilder(NoTgzBuilder):
'--define "_srcrpmdir %s" --define "_rpmdir %s" ' % ( '--define "_srcrpmdir %s" --define "_rpmdir %s" ' % (
self.rpmbuild_sourcedir, self.rpmbuild_builddir, self.rpmbuild_sourcedir, self.rpmbuild_builddir,
self.rpmbuild_basedir, self.rpmbuild_basedir)) self.rpmbuild_basedir, self.rpmbuild_basedir))
# Legacy class name for backward compatability:
class SatelliteBuilder(UpstreamBuilder):
pass

View file

@ -24,7 +24,7 @@ import ConfigParser
from optparse import OptionParser from optparse import OptionParser
from tito.common import DEFAULT_BUILD_DIR from tito.common import DEFAULT_BUILD_DIR
from tito.common import (find_git_root, run_command, from tito.common import (find_git_root, run_command, get_class_by_name,
error_out, debug, get_project_name, get_relative_project_dir, error_out, debug, get_project_name, get_relative_project_dir,
check_tag_exists, get_latest_tagged_version, normalize_class_name) check_tag_exists, get_latest_tagged_version, normalize_class_name)
@ -45,34 +45,6 @@ tagger = tito.tagger.ReleaseTagger
""" """
def get_class_by_name(name):
"""
Get a Python class specified by it's fully qualified name.
NOTE: Does not actually create an instance of the object, only returns
a Class object.
"""
name = normalize_class_name(name)
# Split name into module and class name:
tokens = name.split(".")
class_name = tokens[-1]
module = ""
for s in tokens[0:-1]:
if module:
module = module + "."
module = module + s
mod = __import__(tokens[0])
components = name.split('.')
for comp in components[1:-1]:
mod = getattr(mod, comp)
debug("Importing %s" % name)
c = getattr(mod, class_name)
return c
def read_user_config(): def read_user_config():
config = {} config = {}
file_loc = os.path.expanduser("~/.spacewalk-build-rc") file_loc = os.path.expanduser("~/.spacewalk-build-rc")

View file

@ -60,7 +60,7 @@ def find_git_root():
error_out(["%s does not appear to be within a git checkout." % \ error_out(["%s does not appear to be within a git checkout." % \
os.getcwd()]) os.getcwd()])
if cdup == "": if cdup.strip() == "":
cdup = "./" cdup = "./"
return os.path.abspath(cdup) return os.path.abspath(cdup)
@ -294,3 +294,33 @@ def get_script_path(scriptname):
bin_dir = os.environ['TITO_SRC_BIN_DIR'] bin_dir = os.environ['TITO_SRC_BIN_DIR']
scriptpath = os.path.join(bin_dir, scriptname) scriptpath = os.path.join(bin_dir, scriptname)
return scriptpath return scriptpath
def get_class_by_name(name):
"""
Get a Python class specified by it's fully qualified name.
NOTE: Does not actually create an instance of the object, only returns
a Class object.
"""
name = normalize_class_name(name)
# Split name into module and class name:
tokens = name.split(".")
class_name = tokens[-1]
module = ""
for s in tokens[0:-1]:
if module:
module = module + "."
module = module + s
mod = __import__(tokens[0])
components = name.split('.')
for comp in components[1:-1]:
mod = getattr(mod, comp)
debug("Importing %s" % name)
c = getattr(mod, class_name)
return c

View file

@ -0,0 +1,15 @@
# Copyright (c) 2008-2009 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
"""
Tito Strategies
"""

View file

@ -0,0 +1,106 @@
# Copyright (c) 2008-2010 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
"""
Tito strategies for patching upstream projects.
"""
import re
import os.path
from tito.common import (debug, run_command)
class DefaultPatcher(object):
"""
Default Patcher
Simple generates a single patch by diffing the upstream git tag with the
tag being build.
"""
def __init__(self, builder):
self.builder = builder
def run(self):
patch_filename = "%s-to-%s-%s.patch" % (self.builder.upstream_tag,
self.builder.project_name, self.builder.build_version)
patch_file = os.path.join(self.builder.rpmbuild_gitcopy,
patch_filename)
patch_dir = self.builder.git_root
if self.builder.relative_project_dir != "/":
patch_dir = os.path.join(self.builder.git_root,
self.builder.relative_project_dir)
os.chdir(patch_dir)
debug("patch dir = %s" % patch_dir)
print("Generating patch [%s]" % patch_filename)
debug("Patch: %s" % patch_file)
patch_command = "git diff --relative %s..%s > %s" % \
(self.builder.upstream_tag, self.builder.git_commit_id,
patch_file)
debug("Generating patch with: %s" % patch_command)
output = run_command(patch_command)
print(output)
# Creating two copies of the patch here in the temp build directories
# just out of laziness. Some builders need sources in SOURCES and
# others need them in the git copy. Being lazy here avoids one-off
# hacks and both copies get cleaned up anyhow.
run_command("cp %s %s" % (patch_file, self.builder.rpmbuild_sourcedir))
# Insert patches into the spec file we'll be building:
f = open(self.builder.spec_file, 'r')
lines = f.readlines()
patch_pattern = re.compile('^Patch(\d+):')
source_pattern = re.compile('^Source\d+:')
# Find the largest PatchX: line, or failing that SourceX:
patch_number = 0 # What number should we use for our PatchX line
patch_insert_index = 0 # Where to insert our PatchX line in the list
patch_apply_index = 0 # Where to insert our %patchX line in the list
array_index = 0 # Current index in the array
for line in lines:
match = source_pattern.match(line)
if match:
patch_insert_index = array_index + 1
match = patch_pattern.match(line)
if match:
patch_insert_index = array_index + 1
patch_number = int(match.group(1)) + 1
if line.startswith("%prep"):
# We'll apply patch right after prep if there's no %setup line
patch_apply_index = array_index + 2
elif line.startswith("%setup"):
patch_apply_index = array_index + 2 # already added a line
array_index += 1
debug("patch_insert_index = %s" % patch_insert_index)
debug("patch_apply_index = %s" % patch_apply_index)
if patch_insert_index == 0 or patch_apply_index == 0:
error_out("Unable to insert PatchX or %patchX lines in spec file")
lines.insert(patch_insert_index, "Patch%s: %s\n" % (patch_number,
patch_filename))
lines.insert(patch_apply_index, "%%patch%s -p1\n" % (patch_number))
f.close()
# Now write out the modified lines to the spec file copy:
f = open(self.builder.spec_file, 'w')
for line in lines:
f.write(line)
f.close()