New option for creating a git-meta file
[services/obs-service-git-buildpackage.git] / obs_service_gbp / command.py
1 # vim:fileencoding=utf-8:et:ts=4:sw=4:sts=4
2 #
3 # Copyright (C) 2013 Intel Corporation <markus.lehtonen@linux.intel.com>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
19 """The git-buildpackage source service for OBS"""
20
21 import os
22 import argparse
23 import shutil
24 import tempfile
25 from ConfigParser import SafeConfigParser
26
27 from gbp.rpm import guess_spec, NoSpecError
28 from gbp.scripts.buildpackage import main as gbp_deb
29 from gbp.scripts.buildpackage_rpm import main as gbp_rpm
30
31 from obs_service_gbp import LOGGER, gbplog
32 from obs_service_gbp_utils import GbpServiceError, fork_call, sanitize_uid_gid
33 from obs_service_gbp_utils import write_treeish_meta
34 from gbp_repocache import CachedRepo, CachedRepoError
35 import gbp_repocache
36
37 def have_spec(directory):
38     """Find if the package has spec files"""
39     try:
40         guess_spec(directory, recursive=True)
41     except NoSpecError as err:
42         if str(err).startswith('No spec file'):
43             return False
44     return True
45
46 def construct_gbp_args(args, config, outdir):
47     """Construct args list for git-buildpackage-rpm"""
48     # Args common to deb and rpm
49     argv_common = ['--git-ignore-branch',
50                    '--git-no-hooks',
51                    '--git-export-dir=%s' % outdir,
52                    '--git-tmp-dir=%s' % config['gbp-tmp-dir']]
53     if args.revision:
54         argv_common.append('--git-export=%s' % args.revision)
55     if args.verbose == 'yes':
56         argv_common.append('--git-verbose')
57
58     # Dermine deb and rpm specific args
59     argv_rpm = ['git-buildpackage-rpm'] + argv_common
60     argv_rpm.extend(['--git-builder=osc',
61                      '--git-export-only',
62                      '--git-ignore-branch'])
63     if args.spec_vcs_tag:
64         argv_rpm.append('--git-spec-vcs-tag=%s' % args.spec_vcs_tag)
65
66     # We need to build this way (i.e. run outside the sources directory)
67     # because if run with '-b .' dpkg-source will put it's output to different
68     # directory, depending on the version of dpkg
69     deb_builder_script = 'cd ..; dpkg-source -b $GBP_BUILD_DIR'
70     argv_deb = ['git-buildpackage'] + argv_common
71     argv_deb.extend(['--git-purge',
72                      '--git-builder=%s' % deb_builder_script])
73     return (argv_rpm, argv_deb)
74
75 def read_config(filenames):
76     '''Read configuration file(s)'''
77     defaults = {'repo-cache-dir': '/var/cache/obs/git-buildpackage-repos/',
78                 'gbp-tmp-dir': '/tmp/obs-service-gbp/',
79                 'gbp-user': None,
80                 'gbp-group': None}
81
82     filenames = [os.path.expanduser(fname) for fname in filenames]
83     LOGGER.debug('Trying %s config files: %s', len(filenames), filenames)
84     parser = SafeConfigParser(defaults=defaults)
85     read = parser.read(filenames)
86     LOGGER.debug('Read %s config files: %s', len(read), read)
87
88     # Add our one-and-only section, if it does not exist
89     if not parser.has_section('general'):
90         parser.add_section('general')
91
92     # Read overrides from environment
93     for key in defaults.keys():
94         envvar ='OBS_GIT_BUILDPACKAGE_%s' % key.replace('-', '_').upper()
95         if envvar in os.environ:
96             parser.set('general', key, os.environ[envvar])
97
98     # We only use keys from one section, for now
99     return dict(parser.items('general'))
100
101 def gbp_export(repo, args, config):
102     """Export sources with GBP"""
103     # Create output directories
104     try:
105         if not os.path.exists(args.outdir):
106             os.makedirs(args.outdir)
107         tmp_out = tempfile.mkdtemp(dir=args.outdir)
108     except OSError as err:
109         LOGGER.error('Failed to create output directory: %s', err)
110         return 1
111     # Determine UID/GID
112     try:
113         uid, gid = sanitize_uid_gid(config['gbp-user'], config['gbp-group'])
114     except GbpServiceError as err:
115         LOGGER.error(err)
116         return 1
117     # Make temp outdir accessible to the GBP UID/GID
118     os.chown(tmp_out, uid, gid)
119
120     # Call GBP
121     rpm_args, deb_args = construct_gbp_args(args, config, tmp_out)
122     orig_dir = os.path.abspath(os.curdir)
123     try:
124         os.chdir(repo.repodir)
125         specs_found = have_spec('.')
126         if args.rpm == 'yes' or (args.rpm == 'auto' and specs_found):
127             LOGGER.info('Exporting RPM packaging files with GBP')
128             LOGGER.debug('git-buildpackage-rpm args: %s', ' '.join(rpm_args))
129             ret = fork_call(uid, gid, gbp_rpm, rpm_args)
130             if ret:
131                 LOGGER.error('Git-buildpackage-rpm failed, unable to export '
132                              'RPM packaging files')
133                 return 2
134         if args.deb == 'yes' or (args.deb== 'auto' and os.path.isdir('debian')):
135             LOGGER.info('Exporting Debian source package with GBP')
136             LOGGER.debug('git-buildpackage args: %s', ' '.join(deb_args))
137             ret = fork_call(uid, gid, gbp_deb, deb_args)
138             if ret:
139                 LOGGER.error('Git-buildpackage failed, unable to export Debian '
140                              'sources package files')
141                 return 3
142         for fname in os.listdir(tmp_out):
143             shutil.move(os.path.join(tmp_out, fname),
144                         os.path.join(args.outdir, fname))
145     except GbpServiceError as err:
146         LOGGER.error('Internal service error when trying to run GBP: %s', err)
147         LOGGER.error('This is most likely a configuration error (or a BUG)!')
148         return 1
149     finally:
150         os.chdir(orig_dir)
151         shutil.rmtree(tmp_out)
152     return 0
153
154 def parse_args(argv):
155     """Argument parser"""
156     default_configs = ['/etc/obs/services/git-buildpackage',
157                        '~/.obs/git-buildpackage']
158
159     parser = argparse.ArgumentParser()
160     parser.add_argument('--url', help='Remote repository URL', required=True)
161     parser.add_argument('--outdir', default='.', help='Output direcory')
162     parser.add_argument('--revision', help='Remote repository URL',
163                         default='HEAD')
164     parser.add_argument('--rpm', choices=['auto', 'yes', 'no'], default='auto',
165                         help='Export RPM packaging files')
166     parser.add_argument('--deb', choices=['auto', 'yes', 'no'], default='auto',
167                         help='Export Debian packaging files')
168     parser.add_argument('--verbose', '-v', help='Verbose output',
169                         choices=['yes', 'no'])
170     parser.add_argument('--spec-vcs-tag', help='Set/update the VCS tag in the'
171                                                'spec file')
172     parser.add_argument('--config', default=default_configs, action='append',
173                         help='Config file to use, can be given multiple times')
174     parser.add_argument('--git-meta', metavar='FILENAME',
175                         help='Write data about the exported revision into '
176                              'FILENAME in json format')
177     args = parser.parse_args(argv)
178     args.outdir = os.path.abspath(args.outdir)
179     return args
180
181 def main(argv=None):
182     """Main function"""
183
184     args = parse_args(argv)
185
186     LOGGER.info('Starting git-buildpackage source service')
187     if args.verbose == 'yes':
188         gbplog.setup(color='auto', verbose=True)
189         LOGGER.setLevel(gbplog.DEBUG)
190         gbp_repocache.LOGGER.setLevel(gbplog.DEBUG)
191
192     config = read_config(args.config)
193
194     # Create / update cached repository
195     try:
196         repo = CachedRepo(config['repo-cache-dir'], args.url)
197         args.revision = repo.update_working_copy(args.revision)
198     except CachedRepoError as err:
199         LOGGER.error('RepoCache: %s', str(err))
200         return 1
201
202     # Run GBP
203     ret = gbp_export(repo, args, config)
204
205     # Write git meta file
206     if not ret and args.git_meta:
207         try:
208             write_treeish_meta(repo.repo, args.revision, args.outdir,
209                                args.git_meta)
210         except GbpServiceError as err:
211             LOGGER.error(err)
212             ret = 1
213     return ret