summaryrefslogtreecommitdiffstats
path: root/rpmci/rpmci_srpm_builder_main.py
blob: 9bc260ad33d47866554baa362116e75461368672 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#!/usr/bin/python

# rpmci_srpm_builder_main.py:
# Implementation of rpm-vcs-mirror
#
# Licensed under the new-BSD license (http://www.opensource.org/licenses/bsd-license.php)
# Copyright (C) 2010 Red Hat, Inc.
# Written by Colin Walters <walters@verbum.org>

import os
import sys
import time
import shutil
import optparse
from ConfigParser import SafeConfigParser
import logging
import tempfile
import subprocess
import urllib

import glib
import gobject
import gio

from . import msgqueue
from . import subtask
from . import versioned_repos
from . import artifact
from . import spec
from . import rpmutils
from . import lame_vcs_abstraction

class SRPMBuilder(object):
    def __init__(self, options, config):
        self.config = config

        self._options = options

        mirror_dir = config.get('VCS', 'mirror_dir')

        vcs_msgqueue_dir = config.get('VCS', 'msgqueue')
        self._vcs_msgqueue = msgqueue.MessageQueue(vcs_msgqueue_dir)

        srpm_msgqueue_dir = config.get('SRPM', 'msgqueue')
        self._srpm_msgqueue = msgqueue.MessageQueue(srpm_msgqueue_dir)

        self._artifactset = artifact.ArtifactSet.from_config(config)
        self._target_vcs = {}
        for target in self._artifactset.get_build_targets():
            fedora_url = artifact.fedora_git_url_for_build_target(self.config, target)
            upstream_url = artifact.upstream_vcs_url_for_build_target(self.config, target)
            fedora_vcs = lame_vcs_abstraction.Vcs.new_from_spec(fedora_url)
            fedora_vcs.set_directory(os.path.join(mirror_dir, urllib.quote(fedora_vcs.get_url_string(), '')))
            upstream_vcs = lame_vcs_abstraction.Vcs.new_from_spec(upstream_url)
            upstream_vcs.set_directory(os.path.join(mirror_dir, urllib.quote(upstream_vcs.get_url_string(), '')))
            self._target_vcs[target] = (fedora_vcs, upstream_vcs)
        
        self._srcdir = config.get('SRPM', 'srpm_dir')
        if not os.path.isdir(self._srcdir):
            os.makedirs(self._srcdir)
        self._srcrepo = versioned_repos.VersionedRepo(self._srcdir)

    def start(self):
        self._vcs_msgqueue.connect(self._on_vcs_message)

    def _get_base_rpmbuild_args(self, src_dirpath):
        return ['--define', '_sourcedir ' + src_dirpath,
                '--define', '_specdir ' + src_dirpath,
                '--define', '_builddir ' + src_dirpath,
                '--define', '_srcrpmdir ' + src_dirpath,
                '--define', '_rpmdir ' + src_dirpath]

    def _extract_specfile_from_srpm(self, srpm_path, destpath):
        tmpdir = tempfile.mkdtemp('', 'extract-srpm-spec-tmp')
        name = rpmutils.get_rpm_name(os.path.basename(srpm_path))
        task_logpath = subtask.prepare_task_logfile('extract-srpm-%s' % (name, ))
        logf = open(task_logpath, 'w')
        devnull = open(os.devnull, 'w')
        rpm2cpio = subprocess.Popen(['rpm2cpio', srpm_path], stdout=subprocess.PIPE,
                                    stderr=logf)
        cpio = subprocess.Popen(['cpio', '--quiet', '-i', '*.spec'], cwd=tmpdir,
                                stdin=rpm2cpio.stdout, stdout=devnull,
                                stderr=devnull)
        cpio.wait()
        for filename in os.listdir(tmpdir):
            if filename.endswith('.spec'):
                shutil.move(os.path.join(tmpdir, filename), destpath)
                shutil.rmtree(tmpdir)
                return True
        shutil.rmtree(tmpdir)
        return False
        
    def _on_vcs_message(self, q, messages):
        msg_list = list(messages)
        num_msgs = len(msg_list)
        logging.debug("Starting processing of %d messages" % (num_msgs, ))
        updated_vcs_urls = set()
        for msg in msg_list:
            url = msg.payload['url']
            updated_vcs_urls.add(url)
            q.consume(msg)

        logging.debug("Creating SRPMs for VCS urls: %r" % (updated_vcs_urls, ))
        work_dir = tempfile.mkdtemp('.tmp', 'srpm-builder')
        updated_srpms = set()
        updated_srpm_names = set()
        for url in updated_vcs_urls:
            srpm = self._create_srpm_for_updated_vcs_url(work_dir, url)
            updated_srpms.add(srpm)
            updated_srpm_names.add(rpmutils.get_rpm_name(os.path.basename(srpm)))
        commitid = self._srcrepo.commit_sync(updated_srpms)
        logging.info("New repository revision %d updates SRPMs=%r" % (commitid, updated_srpm_names))
        shutil.rmtree(work_dir)
        self._srpm_msgqueue.append(msgqueue.Message(None, {'type': 'srpm'}, {'version': commitid}))
        logging.debug("Processed %d messages successfully" % (num_msgs, ))

    def _create_srpm_for_updated_vcs_url(self, work_dir, url):
        is_fedora = False
        target = None
        valid_urls = []
        for iter_target,(fedora_vcs, upstream_vcs) in self._target_vcs.iteritems():
            fedora_url = fedora_vcs.get_url_string()
            is_fedora = (url == fedora_url)
            if not is_fedora:
                upstream_url = upstream_vcs.get_url_string()
                if not url == upstream_url:
                    valid_urls.append(upstream_url)
                    continue
            target = iter_target
            break
        assert target is not None, ("Couldn't find target for url %r; valid=%r" % (url, valid_urls))
        (fedora_vcs, upstream_vcs) = self._target_vcs[target]

        logging.debug("Creating new SRPM for %r in %r" % (target.module, work_dir))
        fedora_dir = os.path.join(work_dir, target.module)
        fedora_vcs.export_directory('HEAD', fedora_dir, sys.stderr) 

        specname = target.module + '.spec'
        target_spec_path = os.path.join(fedora_dir, specname)
        target_spec_obj = spec.Spec(target_spec_path)

        latest_repopath = self._srcrepo.get_latest_path()
        latest_repo = self._srcrepo.get_latest_version()
        orig_spec_path = None
        if latest_repo:
            for rpmpath in latest_repo.iter_rpms():
                filename = os.path.basename(rpmpath)
                assert filename.endswith('.src.rpm')
                name = rpmutils.get_rpm_name(filename)
                if name == target.module:
                    orig_spec_path = os.path.join(work_dir, specname)
                    if not self._extract_specfile_from_srpm(rpmpath, orig_spec_path):
                        raise ValueError("Failed to extract specfile from %r" % (rpmpath, ))
                    break
        if orig_spec_path is not None:
            # Okay, we have a previous build, so now we "merge" by taking everything
            # from the new spec file, except we use the Version/Release from the old
            # one.  This is necessary to pacify RPM's insistence on globally
            # ordering versions, rather than simply having repository build versions.
            orig_spec_obj = spec.Spec(orig_spec_path)
            orig_version = orig_spec_obj.get_key('Version')
            orig_release = orig_spec_obj.get_key('Release')

            logging.info("Will update %r from version=%r release=%r" % (target.module, orig_version, orig_release))

            target_spec_obj.set_key('Version', orig_version)
            target_spec_obj.set_key('Release', orig_release)
            target_spec_obj.save()
            
        exec_basedir = os.path.dirname(sys.argv[0])

        args = [os.path.abspath(os.path.join(exec_basedir, 'rpmci-spec-vcs')),
                '--vcsdir=' + upstream_vcs.get_directory(),
                'set-revision', 'HEAD']
        subtask.spawn_sync('rpmci-spec-vcs-%s' % (target.module, ),
                           args, cwd=fedora_dir)
        target_spec_obj = spec.Spec(target_spec_path)
        new_version = target_spec_obj.get_key('Version')
        new_release = target_spec_obj.get_key('Release')
        
        logging.info("Updated %r to version=%r release=%r" % (target.module, new_version, new_release))

        args = ['rpmbuild']
        args.extend(self._get_base_rpmbuild_args(fedora_dir))
        args.extend(['-bs', specname])
        subtask.spawn_sync('rpmbuild-srpm-%s' % (target.module, ),
                           args, cwd=fedora_dir)
        srpm_path = None
        srpm_basename = None
        for filename in os.listdir(fedora_dir):
            if filename.endswith('.src.rpm'):
                srpm_basename = filename
                srpm_path = os.path.join(fedora_dir, filename)
                break
        assert srpm_path is not None
        return srpm_path

def main():
    if hasattr('glib', 'threads_init'):
        glib.threads_init()

    opts = optparse.OptionParser("usage: %prog [options]")
    opts.add_option('-c', '--config', dest='config', help="Path to configuration file")
    opts.add_option('', '--debug', action='store_true', help="Print verbose debugging")

    (options, args) = opts.parse_args()

    if options.config is None:
        print "Must specify --config"
        sys.exit(1)

    config = SafeConfigParser({'home': os.environ['HOME']})
    config.read(options.config)
    level = logging.DEBUG if options.debug else logging.INFO
    logging.basicConfig(stream=sys.stderr, level=level)

    subtask.global_configure(config)

    builder = SRPMBuilder(options, config)
    builder.start()
    
    logging.info("Awaiting events")
    loop = glib.MainLoop()
    loop.run()