Statistics
| Branch: | Tag: | Revision:

root / devflow / autopkg.py @ 938a4739

History | View | Annotate | Download (13.2 kB)

1
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
"""Helper script for automatic build of debian packages."""
35

    
36
import os
37
import sys
38

    
39
from git import GitCommandError
40
from optparse import OptionParser
41
from sh import mktemp, cd, rm, git_dch  # pylint: disable=E0611
42

    
43
from devflow import versioning
44
from devflow import utils
45
from devflow import BRANCH_TYPES
46

    
47
if sys.stdout.isatty():
48
    try:
49
        import colors
50
        use_colors = True
51
    except AttributeError:
52
        use_colors = False
53
else:
54
    use_colors = False
55

    
56

    
57
if use_colors:
58
    red = colors.red
59
    green = colors.green
60
else:
61
    red = lambda x: x
62
    green = lambda x: x
63

    
64
print_red = lambda x: sys.stdout.write(red(x) + "\n")
65
print_green = lambda x: sys.stdout.write(green(x) + "\n")
66

    
67
AVAILABLE_MODES = ["release", "snapshot"]
68

    
69
DESCRIPTION = """Tool for automatical build of debian packages.
70

71
%(prog)s is a helper script for automatic build of debian packages from
72
repositories that follow the `git flow` development model
73
<http://nvie.com/posts/a-successful-git-branching-model/>.
74

75
This script must run from inside a clean git repository and will perform the
76
following steps:
77
    * Clone your repository to a temporary directory
78
    * Merge the current branch with the corresponding debian branch
79
    * Compute the version of the new package and update the python
80
      version files
81
    * Create a new entry in debian/changelog, using `git-dch`
82
    * Create the debian packages, using `git-buildpackage`
83
    * Tag the appropriate branches if in `release` mode
84

85
%(prog)s will work with the packages that are declared in `autopkg.conf`
86
file, which must exist in the toplevel directory of the git repository.
87

88
"""
89

    
90

    
91
def print_help(prog):
92
    print DESCRIPTION % {"prog": prog}
93

    
94

    
95
def main():
96
    from devflow.version import __version__  # pylint: disable=E0611,F0401
97
    parser = OptionParser(usage="usage: %prog [options] mode",
98
                          version="devflow %s" % __version__,
99
                          add_help_option=False)
100
    parser.add_option("-h", "--help",
101
                      action="store_true",
102
                      default=False,
103
                      help="show this help message")
104
    parser.add_option("-k", "--keep-repo",
105
                      action="store_true",
106
                      dest="keep_repo",
107
                      default=False,
108
                      help="Do not delete the cloned repository")
109
    parser.add_option("-b", "--build-dir",
110
                      dest="build_dir",
111
                      default=None,
112
                      help="Directory to store created pacakges")
113
    parser.add_option("-r", "--repo-dir",
114
                      dest="repo_dir",
115
                      default=None,
116
                      help="Directory to clone repository")
117
    parser.add_option("-d", "--dirty",
118
                      dest="force_dirty",
119
                      default=False,
120
                      action="store_true",
121
                      help="Do not check if working directory is dirty")
122
    parser.add_option("-c", "--config-file",
123
                      dest="config_file",
124
                      help="Override default configuration file")
125
    parser.add_option("--no-sign",
126
                      dest="sign",
127
                      action="store_false",
128
                      default=True,
129
                      help="Do not sign the packages")
130
    parser.add_option("--key-id",
131
                      dest="keyid",
132
                      help="Use this keyid for gpg signing")
133
    parser.add_option("--dist",
134
                      dest="dist",
135
                      default=None,
136
                      help="Force distribution in Debian changelog"),
137
    parser.add_option("-S", "--source-only",
138
                      dest="source_only",
139
                      default=False,
140
                      action="store_true",
141
                      help="Specifies a source-only build, no binary packages"
142
                           " need to be made.")
143
    parser.add_option("--debian-branch",
144
                      dest="debian_branch",
145
                      default=None,
146
                      help="Use this debian branch, instead of"
147
                           "auto-discovering the debian branch to use")
148
    parser.add_option("--push-back",
149
                      dest="push_back",
150
                      default=False,
151
                      action="store_true",
152
                      help="Automatically push branches and tags to repo.")
153

    
154
    (options, args) = parser.parse_args()
155

    
156
    if options.help:
157
        print_help(parser.get_prog_name())
158
        parser.print_help()
159
        return
160

    
161
    # Get build mode
162
    try:
163
        mode = args[0]
164
    except IndexError:
165
        mode = utils.get_build_mode()
166
    if mode not in AVAILABLE_MODES:
167
        raise ValueError(red("Invalid argument! Mode must be one: %s"
168
                         % ", ".join(AVAILABLE_MODES)))
169

    
170
    # Load the repository
171
    original_repo = utils.get_repository()
172

    
173
    # Check that repository is clean
174
    toplevel = original_repo.working_dir
175
    if original_repo.is_dirty() and not options.force_dirty:
176
        raise RuntimeError(red("Repository %s is dirty." % toplevel))
177

    
178
    # Get packages from configuration file
179
    config = utils.get_config(options.config_file)
180
    packages = config['packages'].keys()
181
    print_green("Will build the following packages:\n" + "\n".join(packages))
182

    
183
    # Get current branch name and type and check if it is a valid one
184
    branch = original_repo.head.reference.name
185
    branch = utils.undebianize(branch)
186
    branch_type_str = utils.get_branch_type(branch)
187

    
188
    if branch_type_str not in BRANCH_TYPES.keys():
189
        allowed_branches = ", ".join(BRANCH_TYPES.keys())
190
        raise ValueError("Malformed branch name '%s', cannot classify as"
191
                         " one of %s" % (branch, allowed_branches))
192

    
193
    # Fix needed environment variables
194
    v = utils.get_vcs_info()
195
    os.environ["DEVFLOW_BUILD_MODE"] = mode
196
    os.environ["DEBFULLNAME"] = v.name
197
    os.environ["DEBEMAIL"] = v.email
198

    
199
    # Check that base version file and branch are correct
200
    versioning.get_python_version()
201

    
202
    # Get the debian branch
203
    if options.debian_branch:
204
        debian_branch = options.debian_branch
205
    else:
206
        debian_branch = utils.get_debian_branch(branch)
207
    origin_debian = "origin/" + debian_branch
208

    
209
    # Clone the repo
210
    repo_dir = options.repo_dir or create_temp_directory("df-repo")
211
    repo_dir = os.path.abspath(repo_dir)
212
    repo = original_repo.clone(repo_dir, branch=branch)
213
    print_green("Cloned repository to '%s'." % repo_dir)
214

    
215
    build_dir = options.build_dir or create_temp_directory("df-build")
216
    build_dir = os.path.abspath(build_dir)
217
    print_green("Build directory: '%s'" % build_dir)
218

    
219
    # Create the debian branch
220
    repo.git.branch(debian_branch, origin_debian)
221
    print_green("Created branch '%s' to track '%s'" % (debian_branch,
222
                origin_debian))
223

    
224
    # Go to debian branch
225
    repo.git.checkout(debian_branch)
226
    print_green("Changed to branch '%s'" % debian_branch)
227

    
228
    # Merge with starting branch
229
    repo.git.merge(branch)
230
    print_green("Merged branch '%s' into '%s'" % (branch, debian_branch))
231

    
232
    # Compute python and debian version
233
    cd(repo_dir)
234
    python_version = versioning.get_python_version()
235
    debian_version = versioning.\
236
        debian_version_from_python_version(python_version)
237
    print_green("The new debian version will be: '%s'" % debian_version)
238

    
239
    # Update the version files
240
    versioning.update_version()
241

    
242

    
243
    if not options.sign:
244
        sign_tag_opt = None
245
    elif options.keyid:
246
        sign_tag_opt = "-u=%s" % options.keyid
247
    elif mode == "release":
248
        sign_tag_opt = "-s"
249
    else:
250
        sign_tag_opt = None
251

    
252
    # Tag branch with python version
253
    branch_tag = python_version
254
    tag_message = "%s version %s" % (mode.capitalize(), python_version)
255
    try:
256
        repo.git.tag(branch_tag, branch, sign_tag_opt, "-m=%s" % tag_message)
257
    except GitCommandError:
258
        # Tag may already exist, if only the debian branch has changed
259
        pass
260
    upstream_tag = "upstream/" + branch_tag
261
    repo.git.tag(upstream_tag, branch)
262

    
263
    # Update changelog
264
    dch = git_dch("--debian-branch=%s" % debian_branch,
265
                  "--git-author",
266
                  "--ignore-regex=\".*\"",
267
                  "--multimaint-merge",
268
                  "--since=HEAD",
269
                  "--new-version=%s" % debian_version)
270
    print_green("Successfully ran '%s'" % " ".join(dch.cmd))
271

    
272
    if options.dist is not None:
273
        distribution = options.dist
274
    elif mode == "release":
275
        distribution = utils.get_distribution_codename()
276
    else:
277
        distribution = "unstable"
278

    
279
    f = open("debian/changelog", 'r+')
280
    lines = f.readlines()
281
    lines[0] = lines[0].replace("UNRELEASED", distribution)
282
    lines[2] = lines[2].replace("UNRELEASED", "%s build" % mode)
283
    f.seek(0)
284
    f.writelines(lines)
285
    f.close()
286

    
287
    if mode == "release":
288
        call("vim debian/changelog")
289

    
290
    # Add changelog to INDEX
291
    repo.git.add("debian/changelog")
292
    # Commit Changes
293
    repo.git.commit("-s", "-a", m="Bump version to %s" % debian_version)
294
    # Tag debian branch
295
    debian_branch_tag = "debian/" + utils.version_to_tag(debian_version)
296
    tag_message = "%s version %s" % (mode.capitalize(), debian_version)
297
    if mode == "release":
298
        repo.git.tag(debian_branch_tag, sign_tag_opt, "-m=%s" % tag_message)
299

    
300
    # Add version.py files to repo
301
    call("grep \"__version_vcs\" -r . -l -I | xargs git add -f")
302

    
303
    # Create debian packages
304
    cd(repo_dir)
305
    version_files = []
306
    for _, pkg_info in config['packages'].items():
307
        version_files.append(pkg_info['version_file'])
308
    ignore_regexp = "|".join(["^(%s)$" % vf for vf in version_files])
309
    build_cmd = "git-buildpackage --git-export-dir=%s"\
310
                " --git-upstream-branch=%s --git-debian-branch=%s"\
311
                " --git-export=INDEX --git-ignore-new -sa"\
312
                " --source-option=--auto-commit"\
313
                " --git-upstream-tag=%s"\
314
                % (build_dir, branch, debian_branch, upstream_tag)
315
    if options.source_only:
316
        build_cmd += " -S"
317
    if not options.sign:
318
        build_cmd += " -uc -us"
319
    elif options.keyid:
320
        build_cmd += " -k\"'%s'\"" % options.keyid
321
    call(build_cmd)
322

    
323
    # Remove cloned repo
324
    if mode != 'release' and not options.keep_repo:
325
        print_green("Removing cloned repo '%s'." % repo_dir)
326
        rm("-r", repo_dir)
327

    
328
    # Print final info
329
    info = (("Version", debian_version),
330
            ("Upstream branch", branch),
331
            ("Upstream tag", branch_tag),
332
            ("Debian branch", debian_branch),
333
            ("Debian tag", debian_branch_tag),
334
            ("Repository directory", repo_dir),
335
            ("Packages directory", build_dir))
336
    print_green("\n".join(["%s: %s" % (name, val) for name, val in info]))
337

    
338
    # Print help message
339
    if mode == "release":
340
        origin = original_repo.remote().url
341
        repo.create_remote("original_origin", origin)
342
        print_green("Created remote 'original_origin' for the repository '%s'"
343
                    % origin)
344

    
345
        print_green("To update repositories '%s' and '%s' go to '%s' and run:"
346
                    % (toplevel, origin, repo_dir))
347
        for remote in ['origin', 'original_origin']:
348
            objects = [debian_branch, branch_tag, debian_branch_tag]
349
            print_green("git push %s %s" % (remote, " ".join(objects)))
350
        if options.push_back:
351
            objects = [debian_branch, branch_tag, debian_branch_tag]
352
            repo.git.push("origin", *objects)
353
            print_green("Automatically updated origin repo.")
354

    
355

    
356
def create_temp_directory(suffix):
357
    create_dir_cmd = mktemp("-d", "/tmp/" + suffix + "-XXXXX")
358
    return create_dir_cmd.stdout.strip()
359

    
360

    
361
def call(cmd):
362
    rc = os.system(cmd)
363
    if rc:
364
        raise RuntimeError("Command '%s' failed!" % cmd)
365

    
366

    
367
if __name__ == "__main__":
368
    sys.exit(main())