From e0f3e82ea300c6fe6a5011f92abe6692b5f632a0 Mon Sep 17 00:00:00 2001 From: Devan Goodwin Date: Thu, 20 May 2010 22:05:26 -0300 Subject: [PATCH] Refactor upstream patching to strategy. Will allow projects to swap in more advanced strategies for patching their upstream projects. Also renaming SatelliteBuilder to UpstreamBuilder. --- src/tito/builder.py | 91 +++++------------------------ src/tito/cli.py | 30 +--------- src/tito/common.py | 32 +++++++++- src/tito/strategy/__init__.py | 15 +++++ src/tito/strategy/patcher.py | 106 ++++++++++++++++++++++++++++++++++ 5 files changed, 169 insertions(+), 105 deletions(-) create mode 100644 src/tito/strategy/__init__.py create mode 100644 src/tito/strategy/patcher.py diff --git a/src/tito/builder.py b/src/tito/builder.py index d051d33..5b6de94 100644 --- a/src/tito/builder.py +++ b/src/tito/builder.py @@ -15,7 +15,6 @@ Code for building Spacewalk/Satellite tarballs, srpms, and rpms. """ import os -import re import sys 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, get_relative_project_dir, check_tag_exists, get_commit_count, get_latest_commit) +from tito.common import get_class_by_name DEFAULT_KOJI_OPTS = "build --nowait" DEFAULT_CVS_BUILD_DIR = "cvswork" @@ -583,6 +583,8 @@ class Builder(object): commands.getoutput("rm -rf %s" % self.rpmbuild_dir) debug("Cleaning up [%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): """ @@ -814,11 +816,11 @@ class CvsBuilder(NoTgzBuilder): return rpms -class SatelliteBuilder(NoTgzBuilder): +class UpstreamBuilder(NoTgzBuilder): """ - Builder for packages that are based off some upstream version in Spacewalk - git. Commits applied in Satellite git become patches applied to the - upstream Spacewalk tarball. + Builder for packages that are based off an upstream git tag. + Commits applied in downstream git become patches applied to the + upstream tarball. i.e. satellite-java-0.4.0-5 built from spacewalk-java-0.4.0-1 and any 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: self.upstream_version = None self.upstream_tag = None - self.patch_filename = None - self.patch_file = None # When syncing files with CVS, only copy files with these extensions: self.cvs_copy_extensions = (".spec", ".patch") @@ -899,80 +899,15 @@ class SatelliteBuilder(NoTgzBuilder): return self._generate_patches() - self._insert_patches_into_spec_file() def _generate_patches(self): """ Generate patches for any differences between our tag and the upstream tag. """ - self.patch_filename = "%s-to-%s-%s.patch" % (self.upstream_tag, - self.project_name, self.build_version) - self.patch_file = os.path.join(self.rpmbuild_gitcopy, - 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() + from tito.strategy.patcher import DefaultPatcher + patcher = DefaultPatcher(builder=self) + patcher.run() def _get_upstream_version(self): """ @@ -1008,3 +943,9 @@ class SatelliteBuilder(NoTgzBuilder): '--define "_srcrpmdir %s" --define "_rpmdir %s" ' % ( self.rpmbuild_sourcedir, self.rpmbuild_builddir, self.rpmbuild_basedir, self.rpmbuild_basedir)) + + +# Legacy class name for backward compatability: +class SatelliteBuilder(UpstreamBuilder): + pass + diff --git a/src/tito/cli.py b/src/tito/cli.py index 926af25..3bf9324 100644 --- a/src/tito/cli.py +++ b/src/tito/cli.py @@ -24,7 +24,7 @@ import ConfigParser from optparse import OptionParser 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, 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(): config = {} file_loc = os.path.expanduser("~/.spacewalk-build-rc") diff --git a/src/tito/common.py b/src/tito/common.py index ffcb04e..016e546 100644 --- a/src/tito/common.py +++ b/src/tito/common.py @@ -60,7 +60,7 @@ def find_git_root(): error_out(["%s does not appear to be within a git checkout." % \ os.getcwd()]) - if cdup == "": + if cdup.strip() == "": cdup = "./" return os.path.abspath(cdup) @@ -294,3 +294,33 @@ def get_script_path(scriptname): bin_dir = os.environ['TITO_SRC_BIN_DIR'] scriptpath = os.path.join(bin_dir, scriptname) 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 + + diff --git a/src/tito/strategy/__init__.py b/src/tito/strategy/__init__.py new file mode 100644 index 0000000..f37d8a0 --- /dev/null +++ b/src/tito/strategy/__init__.py @@ -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 +""" diff --git a/src/tito/strategy/patcher.py b/src/tito/strategy/patcher.py new file mode 100644 index 0000000..ca2a705 --- /dev/null +++ b/src/tito/strategy/patcher.py @@ -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() + + + +