This patch adds support for building from git:// respositories. It is used like
koji call build 'git://<REPOSITORY>?<PACKAGE>#<TAG>' <TARGET>
The repository specified above must have the following layout:
<BASE_DIR>/<REPOSITORY> |- common/ | `- .git/ |- ... |- <PACKAGE> | `- .git/ |- ...
This means that every package is hold in an own git repository. There must be a 'common' repository which contains files required for building the srpm.
There is no explicit support for branches; the patch just checks out the given tag and builds from it. Perhaps it can be enhanced to check whether branch-point is some child of the tag...
TODO: the git and cvs handlers share some common, non trivial code. This should be generalized.
TODO: use a separate user account for the 'git' and 'make srpm' operations; currently they are executed under the uid 'kojid' is running (which is 'root). Hence, a '%(/sbin/killall5)' in the spec file can bring down the box.
Signed-off-by: Enrico Scholz enrico.scholz@informatik.tu-chemnitz.de ---
builder/kojid | 130 +++++++++++++++++++++++++++++++++++++++++++- cli/koji | 9 ++- koji.spec | 2 - koji/__init__.py | 11 ++++ www/kojiweb/taskinfo.chtml | 7 ++ www/kojiweb/tasks.chtml | 1 6 files changed, 153 insertions(+), 7 deletions(-)
diff --git a/builder/kojid b/builder/kojid index 5940954..bbb9774 100755 --- a/builder/kojid +++ b/builder/kojid @@ -1396,7 +1396,7 @@ class ChainBuildTask(BaseTaskHandler): subtasks = [] build_tasks = [] for src in build_level: - if src.startswith('cvs://'): + if src.startswith('cvs://') or src.startswith('git://'): task_id = session.host.subtask(method='build', arglist=[src, target, opts], parent=self.id) @@ -1437,8 +1437,10 @@ class BuildTask(BaseTaskHandler): raise koji.BuildError, "arch_override is only allowed for scratch builds" task_info = session.getTaskInfo(self.id) # only allow admins to perform non-scratch builds from srpm - if not src.startswith('cvs://') and not opts.get('scratch') \ - and not 'admin' in session.getUserPerms(task_info['owner']): + if not src.startswith('cvs://') and \ + not src.startswith('git://') and \ + not opts.get('scratch') and \ + not 'admin' in session.getUserPerms(task_info['owner']): raise koji.BuildError, "only admins may peform non-scratch builds from srpm" target_info = session.getBuildTarget(target) if not target_info: @@ -1496,6 +1498,8 @@ class BuildTask(BaseTaskHandler): if isinstance(src,str): if src.startswith('cvs://'): return self.getSRPMFromCVS(src) + if src.startswith('git://'): + return self.getSRPMFromGit(src) else: #assume this is a path under uploads return src @@ -1514,6 +1518,17 @@ class BuildTask(BaseTaskHandler): srpm = result['srpm'] return srpm
+ def getSRPMFromGit(self, url): + #TODO - allow different ways to get the srpm + task_id = session.host.subtask(method='buildSRPMFromGit', + arglist=[url], + label='srpm', + parent=self.id) + # wait for subtask to finish + result = self.wait(task_id)[task_id] + srpm = result['srpm'] + return srpm + def readSRPMHeader(self, srpm): #srpm arg should be a path relative to <BASEDIR>/work global options @@ -1767,6 +1782,115 @@ class TagBuildTask(BaseTaskHandler): exctype, value = sys.exc_info()[:2] session.host.tagNotification(False, tag_id, fromtag, build_id, user_id, ignore_success, "%s: %s" % (exctype, value)) raise e + + +class BuildSRPMFromGitTask(BaseTaskHandler): + + Methods = ['buildSRPMFromGit'] + _taskWeight = 0.75 + + def spec_sanity_checks(self, filename): + spec = open(filename).read() + for tag in ("Packager", "Distribution", "Vendor"): + if re.match("%s:" % tag, spec, re.M): + raise koji.BuildError, "%s is not allowed to be set in spec file" % tag + for tag in ("packager", "distribution", "vendor"): + if re.match("%%define\s+%s\s+" % tag, spec, re.M): + raise koji.BuildError, "%s is not allowed to be defined in spec file" % tag + + def handler(self,url): + if not url.startswith('git://'): + raise koji.BuildError("invalid git URL: %s" % url) + + # Hack it because it refuses to parse it properly otherwise + scheme, netloc, path, params, query, fragment = urlparse.urlparse('http'+url[3:]) + if not (netloc and path and fragment and query): + raise koji.BuildError("invalid git URL: %s" % url) + + # Steps: + # 1. GIT clone into tempdir + # 3. Run 'make srpm' + + gitdir = self.workdir + '/git' + koji.ensuredir(gitdir) + logfile = self.workdir + "/srpm.log" + uploadpath = self.getUploadDir() + sourcedir = '%s/%s' % (gitdir, query) + + cmd = ['git', 'clone', 'git://%s%s/%s' % (netloc, path, query), query] + if log_output(cmd[0], cmd, logfile, uploadpath, cwd=gitdir, logerror=1, append=1): + output = "(none)" + try: + output = open(logfile).read() + except IOError: + pass + raise koji.BuildError, "Error with clone 'git://%s%s/%s: %s" % (netloc, path, query, output) + + cmd = ['git', 'clone', 'git://%s%s/common' % (netloc, path)] + self.logger.debug("executing: %s" % cmd) + if log_output(cmd[0], cmd, logfile, uploadpath, cwd=gitdir, logerror=1, append=1): + output = "(none)" + try: + output = open(logfile).read() + except IOError: + pass + raise koji.BuildError, "Error with clone 'git://%s%s/common: %s" % (netloc, path, output) + + cmd = ['git', 'ls-files', '-t', '-s'] + log_output(cmd[0], cmd, logfile, uploadpath, cwd='%s/common' % gitdir, logerror=1, append=1) + + cmd = ['git', 'checkout', '-b', 'kojibuild', fragment] + self.logger.debug("executing: %s" % cmd) + if log_output(cmd[0], cmd, logfile, uploadpath, cwd=sourcedir, logerror=1, append=1): + output = "(none)" + try: + output = open(logfile).read() + except IOError: + pass + raise koji.BuildError, "Error with checking out tag '%s' from git://%s%s/%s: %s" % (fragment, netloc, path, query, output) + + cmd = ['git', 'ls-files', '-t', '-s'] + log_output(cmd[0], cmd, logfile, uploadpath, cwd=sourcedir, logerror=1, append=1) + + spec_files = glob.glob("%s/*.spec" % sourcedir) + if len(spec_files) == 0: + raise koji.BuildError("No spec file found") + elif len(spec_files) > 1: + raise koji.BuildError("Multiple spec files found: %s" % spec_files) + spec_file = spec_files[0] + + # Run spec file sanity checks. Any failures will throw a BuildError + self.spec_sanity_checks(spec_file) + + #build srpm + cmd = ['make', '-C', sourcedir, 'srpm', '_KOJI=1.2.2', '_KOJI_TAG=%s' % fragment] + self.logger.debug("executing: %s" % cmd) + if log_output(cmd[0], cmd, logfile, uploadpath, cwd=gitdir, logerror=1, append=1): + raise koji.BuildError, "Error building SRPM" + + srpms = glob.glob('%s/*.src.rpm' % sourcedir) + if len(srpms) == 0: + raise koji.BuildError, "No srpms found in %s" % sourcedir + elif len(srpms) > 1: + raise koji.BuildError, "Multiple srpms found in %s: %s" % (sourcedir, ", ".join(srpms)) + else: + srpm = srpms[0] + + # check srpm name + h = koji.get_rpm_header(srpm) + name = h[rpm.RPMTAG_NAME] + version = h[rpm.RPMTAG_VERSION] + release = h[rpm.RPMTAG_RELEASE] + srpm_name = "%(name)s-%(version)s-%(release)s.src.rpm" % locals() + if srpm_name != os.path.basename(srpm): + raise koji.BuildError, 'srpm name mismatch: %s != %s' % (srpm_name, os.path.basename(srpm)) + + #upload srpm and return + self.uploadFile(srpm) + return { + 'srpm' : "%s/%s" % (uploadpath, srpm_name), + 'log' : "%s/srpm.log" % uploadpath, + }
class BuildSRPMFromCVSTask(BaseTaskHandler):
diff --git a/cli/koji b/cli/koji index 718b6e5..4f05500 100755 --- a/cli/koji +++ b/cli/koji @@ -669,7 +669,7 @@ def handle_build(options, session, args): if build_opts.background: #relative to koji.PRIO_DEFAULT priority = 5 - if not source.startswith('cvs://'): + if not (source.startswith('cvs://') or source.startswith('git://')): # only allow admins to perform non-scratch builds from srpm if not opts['scratch'] and not session.hasPerm('admin'): parser.error(_("builds from srpm must use --scratch")) @@ -741,7 +741,7 @@ def handle_chain_build(options, session, args): if build_level: src_list.append(build_level) build_level = [] - elif src.startswith('cvs://'): + elif src.startswith('cvs://') or src.startswith('git://'): build_level.append(src) elif '/' not in src and len(src.split('-')) >= 3: # quick check that it looks like a N-V-R @@ -2432,8 +2432,13 @@ def _parseTaskParams(session, method, task_id): if method == 'buildFromCVS': lines.append("CVS URL: %s" % params[0]) lines.append("Build Target: %s" % params[1]) + elif method == 'buildFromGit': + lines.append("GIT URL: %s" % params[0]) + lines.append("Build Target: %s" % params[1]) elif method == 'buildSRPMFromCVS': lines.append("CVS URL: %s" % params[0]) + elif method == 'buildSRPMFromGit': + lines.append("GIT URL: %s" % params[0]) elif method == 'multiArchBuild': lines.append("SRPM: %s/work/%s" % (options.topdir, params[0])) lines.append("Build Target: %s" % params[1]) diff --git a/koji.spec b/koji.spec index f14bb6e..7d4d643 100644 --- a/koji.spec +++ b/koji.spec @@ -49,7 +49,7 @@ Requires(post): /sbin/service Requires(preun): /sbin/chkconfig Requires(preun): /sbin/service Requires(pre): /usr/sbin/useradd -Requires: cvs +Requires: cvs git-core make Requires: rpm-build Requires: redhat-rpm-config Requires: createrepo >= 0.4.10 diff --git a/koji/__init__.py b/koji/__init__.py index d808126..5789220 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -1517,6 +1517,11 @@ def taskLabel(taskInfo): if source.startswith('cvs://'): source = source[source.rfind('/') + 1:] source = source.replace('#', ':') + elif source.startswith('git://'): + source = source[source.rfind('?') + 1:] + tmp = source[:source.find('#')] + source = source[source.find('#') + 1:] + source = '%s:%s' % (tmp, source[source.rfind('/') + 1:]) else: source = os.path.basename(source) extra = '%s, %s' % (target, source) @@ -1526,6 +1531,12 @@ def taskLabel(taskInfo): url = url[url.rfind('/') + 1:] url = url.replace('#', ':') extra = url + elif method == 'buildSRPMFromGit': + if taskInfo.has_key('request'): + url = taskInfo['request'][0] + url = url[url.rfind('/') + 1:] + url = url.replace('#', ':') + extra = url elif method == 'buildArch': if taskInfo.has_key('request'): srpm, tagID, arch = taskInfo['request'][:3] diff --git a/www/kojiweb/taskinfo.chtml b/www/kojiweb/taskinfo.chtml index 76df4bc..dd5cf72 100644 --- a/www/kojiweb/taskinfo.chtml +++ b/www/kojiweb/taskinfo.chtml @@ -67,6 +67,11 @@ <strong>Build Target:</strong> <a href="buildtargetinfo?name=$params[1]">$params[1]</a> #elif $task.method == 'buildSRPMFromCVS' <strong>CVS URL:</strong> $params[0] + #elif $task.method == 'buildFromGit' + <strong>GIT URL:</strong> $params[0]<br/> + <strong>Build Target:</strong> <a href="buildtargetinfo?name=$params[1]">$params[1]</a> + #elif $task.method == 'buildSRPMFromGit' + <strong>GIT URL:</strong> $params[0] #elif $task.method == 'multiArchBuild' <strong>SRPM:</strong> $params[0]<br/> <strong>Build Target:</strong> <a href="buildtargetinfo?name=$params[1]">$params[1]</a><br/> @@ -276,7 +281,7 @@ $cgi.escape($result.faultString.strip()) <a href="getfile?taskID=$task.id&name=$urllib.quote($filename)">$filename</a><br/> #end for #if $task.state not in ($koji.TASK_STATES.CLOSED, $koji.TASK_STATES.CANCELED, $koji.TASK_STATES.FAILED) and \ - $task.method in ('buildSRPMFromCVS', 'buildArch', 'createrepo') + $task.method in ('buildSRPMFromCVS', 'buildSRPMFromGit', 'buildArch', 'createrepo') <br/> <a href="watchlogs?taskID=$task.id">Watch logs</a> #end if diff --git a/www/kojiweb/tasks.chtml b/www/kojiweb/tasks.chtml index 70de06c..32ca6ad 100644 --- a/www/kojiweb/tasks.chtml +++ b/www/kojiweb/tasks.chtml @@ -104,6 +104,7 @@ All <option value="all" #if $method == 'all' then 'selected="selected"' else ''#>all</option> <option value="build" #if $method == 'build' then 'selected="selected"' else ''#>build</option> <option value="buildSRPMFromCVS" #if $method == 'buildSRPMFromCVS' then 'selected="selected"' else ''#>buildSRPMFromCVS</option> + <option value="buildSRPMFromGit" #if $method == 'buildSRPMFromGit' then 'selected="selected"' else ''#>buildSRPMFromGit</option> <option value="buildArch" #if $method == 'buildArch' then 'selected="selected"' else ''#>buildArch</option> <option value="buildNotification" #if $method == 'buildNotification' then 'selected="selected"' else ''#>buildNotification</option> <option value="tagBuild" #if $method == 'tagBuild' then 'selected="selected"' else ''#>tagBuild</option>
On Sat, 2007-09-15 at 21:31 +0200, Enrico Scholz wrote:
This patch adds support for building from git:// respositories. It is used like
<snip>
TODO: the git and cvs handlers share some common, non trivial code. This should be generalized.
does anyone have any ideas about how to abstract or generalize the SCM backend from koji? the cvs/svn/git SCM backends all perform similar functions and use similar code- what is the best way to make them co-exist with maintainability and extensibility?
rob.
On Mon, 17 Sep 2007 10:06:04 -0400 rob myers rob.myers@gtri.gatech.edu wrote:
does anyone have any ideas about how to abstract or generalize the SCM backend from koji? the cvs/svn/git SCM backends all perform similar functions and use similar code- what is the best way to make them co-exist with maintainability and extensibility?
Probably the best way is to create an "scm_callback" or some such. Each function that deals with scm calls just calls "scm_callback(options)". A config file setting would define which scm_callback to use, either git/svn/hg/cvs/etc... They would all need to take the same options (whether or not they do anything with all the options doesn't matter). This way scm oddities are defined once per scm in the callback definition, and the rest of the koji code just calls generic scm_callback functions.
Make sense?
On Mon, 2007-09-17 at 10:12 -0400, Jesse Keating wrote:
On Mon, 17 Sep 2007 10:06:04 -0400 rob myers rob.myers@gtri.gatech.edu wrote:
does anyone have any ideas about how to abstract or generalize the SCM backend from koji? the cvs/svn/git SCM backends all perform similar functions and use similar code- what is the best way to make them co-exist with maintainability and extensibility?
Probably the best way is to create an "scm_callback" or some such. Each function that deals with scm calls just calls "scm_callback(options)". A config file setting would define which scm_callback to use, either git/svn/hg/cvs/etc... They would all need to take the same options (whether or not they do anything with all the options doesn't matter). This way scm oddities are defined once per scm in the callback definition, and the rest of the koji code just calls generic scm_callback functions.
Make sense?
i think so. i don't have time to code this up right now, but i think it is worth doing. maybe i can find some time to develop a patch in the next couple of weeks. if anyone wants to beat me to it, that'd be great. :)
rob.
buildsys@lists.fedoraproject.org