Statistics
| Branch: | Tag: | Revision:

root / devflow / autopkg.py @ 8f939e17

History | View | Annotate | Download (12.8 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

    
149
    (options, args) = parser.parse_args()
150

    
151
    if options.help:
152
        print_help(parser.get_prog_name())
153
        parser.print_help()
154
        return
155

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

    
165
    # Load the repository
166
    original_repo = utils.get_repository()
167

    
168
    # Check that repository is clean
169
    toplevel = original_repo.working_dir
170
    if original_repo.is_dirty() and not options.force_dirty:
171
        raise RuntimeError(red("Repository %s is dirty." % toplevel))
172

    
173
    # Get packages from configuration file
174
    config = utils.get_config(options.config_file)
175
    packages = config['packages'].keys()
176
    print_green("Will build the following packages:\n" + "\n".join(packages))
177

    
178
    # Get current branch name and type and check if it is a valid one
179
    branch = original_repo.head.reference.name
180
    branch = utils.undebianize(branch)
181
    branch_type_str = utils.get_branch_type(branch)
182

    
183
    if branch_type_str not in BRANCH_TYPES.keys():
184
        allowed_branches = ", ".join(BRANCH_TYPES.keys())
185
        raise ValueError("Malformed branch name '%s', cannot classify as"
186
                         " one of %s" % (branch, allowed_branches))
187

    
188
    # Fix needed environment variables
189
    v = utils.get_vcs_info()
190
    os.environ["DEVFLOW_BUILD_MODE"] = mode
191
    os.environ["DEBFULLNAME"] = v.name
192
    os.environ["DEBEMAIL"] = v.email
193

    
194
    # Check that base version file and branch are correct
195
    versioning.get_python_version()
196

    
197
    # Get the debian branch
198
    if options.debian_branch:
199
        debian_branch = options.debian_branch
200
    else:
201
        debian_branch = utils.get_debian_branch(branch)
202
    origin_debian = "origin/" + debian_branch
203

    
204
    # Clone the repo
205
    repo_dir = options.repo_dir or create_temp_directory("df-repo")
206
    repo_dir = os.path.abspath(repo_dir)
207
    repo = original_repo.clone(repo_dir, branch=branch)
208
    print_green("Cloned repository to '%s'." % repo_dir)
209

    
210
    build_dir = options.build_dir or create_temp_directory("df-build")
211
    build_dir = os.path.abspath(build_dir)
212
    print_green("Build directory: '%s'" % build_dir)
213

    
214
    # Create the debian branch
215
    repo.git.branch(debian_branch, origin_debian)
216
    print_green("Created branch '%s' to track '%s'" % (debian_branch,
217
                origin_debian))
218

    
219
    # Go to debian branch
220
    repo.git.checkout(debian_branch)
221
    print_green("Changed to branch '%s'" % debian_branch)
222

    
223
    # Merge with starting branch
224
    repo.git.merge(branch)
225
    print_green("Merged branch '%s' into '%s'" % (branch, debian_branch))
226

    
227
    # Compute python and debian version
228
    cd(repo_dir)
229
    python_version = versioning.get_python_version()
230
    debian_version = versioning.\
231
        debian_version_from_python_version(python_version)
232
    print_green("The new debian version will be: '%s'" % debian_version)
233

    
234
    # Update the version files
235
    versioning.update_version()
236

    
237

    
238
    if not options.sign:
239
        sign_tag_opt = None
240
    elif options.keyid:
241
        sign_tag_opt = "-u=%s" % options.keyid
242
    elif mode == "release":
243
        sign_tag_opt = "-s"
244
    else:
245
        sign_tag_opt = None
246

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

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

    
267
    if options.dist is not None:
268
        distribution = options.dist
269
    elif mode == "release":
270
        distribution = utils.get_distribution_codename()
271
    else:
272
        distribution = "unstable"
273

    
274
    f = open("debian/changelog", 'r+')
275
    lines = f.readlines()
276
    lines[0] = lines[0].replace("UNRELEASED", distribution)
277
    lines[2] = lines[2].replace("UNRELEASED", "%s build" % mode)
278
    f.seek(0)
279
    f.writelines(lines)
280
    f.close()
281

    
282
    if mode == "release":
283
        call("vim debian/changelog")
284

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

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

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

    
318
    # Remove cloned repo
319
    if mode != 'release' and not options.keep_repo:
320
        print_green("Removing cloned repo '%s'." % repo_dir)
321
        rm("-r", repo_dir)
322

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

    
333
    # Print help message
334
    if mode == "release":
335
        origin = original_repo.remote().url
336
        repo.create_remote("original_origin", origin)
337
        print_green("Created remote 'original_origin' for the repository '%s'"
338
                    % origin)
339

    
340
        print_green("To update repositories '%s' and '%s' go to '%s' and run:"
341
                    % (toplevel, origin, repo_dir))
342
        for remote in ['origin', 'original_origin']:
343
            objects = [debian_branch, branch_tag, debian_branch_tag]
344
            print_green("git push %s %s" % (remote, " ".join(objects)))
345

    
346

    
347
def create_temp_directory(suffix):
348
    create_dir_cmd = mktemp("-d", "/tmp/" + suffix + "-XXXXX")
349
    return create_dir_cmd.stdout.strip()
350

    
351

    
352
def call(cmd):
353
    rc = os.system(cmd)
354
    if rc:
355
        raise RuntimeError("Command '%s' failed!" % cmd)
356

    
357

    
358
if __name__ == "__main__":
359
    sys.exit(main())