Statistics
| Branch: | Tag: | Revision:

root / devflow / autopkg.py @ 42868817

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

    
144
    (options, args) = parser.parse_args()
145

    
146
    if options.help:
147
        print_help(parser.get_prog_name())
148
        parser.print_help()
149
        return
150

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

    
160
    # Load the repository
161
    original_repo = utils.get_repository()
162

    
163
    # Check that repository is clean
164
    toplevel = original_repo.working_dir
165
    if original_repo.is_dirty() and not options.force_dirty:
166
        raise RuntimeError(red("Repository %s is dirty." % toplevel))
167

    
168
    # Get packages from configuration file
169
    config = utils.get_config(options.config_file)
170
    packages = config['packages'].keys()
171
    print_green("Will build the following packages:\n" + "\n".join(packages))
172

    
173
    # Get current branch name and type and check if it is a valid one
174
    branch = original_repo.head.reference.name
175
    branch = utils.undebianize(branch)
176
    branch_type_str = utils.get_branch_type(branch)
177

    
178
    if branch_type_str not in BRANCH_TYPES.keys():
179
        allowed_branches = ", ".join(BRANCH_TYPES.keys())
180
        raise ValueError("Malformed branch name '%s', cannot classify as"
181
                         " one of %s" % (branch, allowed_branches))
182

    
183
    # Fix needed environment variables
184
    os.environ["DEVFLOW_BUILD_MODE"] = mode
185
    git_config = original_repo.config_reader()
186
    try:
187
        os.environ["DEBFULLNAME"] = git_config.get_value("user", "name")
188
        os.environ["DEBEMAIL"] = git_config.get_value("user", "email")
189
    except:
190
        print "Could not load user/email from config"
191

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

    
195
    # Get the debian branch
196
    debian_branch = utils.get_debian_branch(branch)
197
    origin_debian = "origin/" + debian_branch
198

    
199
    # Clone the repo
200
    repo_dir = options.repo_dir or create_temp_directory("df-repo")
201
    repo_dir = os.path.abspath(repo_dir)
202
    repo = original_repo.clone(repo_dir, branch=branch)
203
    print_green("Cloned repository to '%s'." % repo_dir)
204

    
205
    build_dir = options.build_dir or create_temp_directory("df-build")
206
    build_dir = os.path.abspath(build_dir)
207
    print_green("Build directory: '%s'" % build_dir)
208

    
209
    # Create the debian branch
210
    repo.git.branch(debian_branch, origin_debian)
211
    print_green("Created branch '%s' to track '%s'" % (debian_branch,
212
                origin_debian))
213

    
214
    # Go to debian branch
215
    repo.git.checkout(debian_branch)
216
    print_green("Changed to branch '%s'" % debian_branch)
217

    
218
    # Merge with starting branch
219
    repo.git.merge(branch)
220
    print_green("Merged branch '%s' into '%s'" % (branch, debian_branch))
221

    
222
    # Compute python and debian version
223
    cd(repo_dir)
224
    python_version = versioning.get_python_version()
225
    debian_version = versioning.\
226
        debian_version_from_python_version(python_version)
227
    print_green("The new debian version will be: '%s'" % debian_version)
228

    
229
    # Update the version files
230
    versioning.update_version()
231

    
232
    # Tag branch with python version
233
    branch_tag = python_version
234
    try:
235
        repo.git.tag(branch_tag, branch)
236
    except GitCommandError:
237
        # Tag may already exist, if only the debian branch has changed
238
        pass
239
    upstream_tag = "upstream/" + branch_tag
240
    repo.git.tag(upstream_tag, branch)
241

    
242
    # Update changelog
243
    dch = git_dch("--debian-branch=%s" % debian_branch,
244
                  "--git-author",
245
                  "--ignore-regex=\".*\"",
246
                  "--multimaint-merge",
247
                  "--since=HEAD",
248
                  "--new-version=%s" % debian_version)
249
    print_green("Successfully ran '%s'" % " ".join(dch.cmd))
250

    
251
    if options.dist is not None:
252
        distribution = options.dist
253
    elif mode == "release":
254
        distribution = utils.get_distribution_codename()
255
    else:
256
        distribution = "unstable"
257

    
258
    f = open("debian/changelog", 'r+')
259
    lines = f.readlines()
260
    lines[0] = lines[0].replace("UNRELEASED", distribution)
261
    lines[2] = lines[2].replace("UNRELEASED", "%s build" % mode)
262
    f.seek(0)
263
    f.writelines(lines)
264
    f.close()
265

    
266
    if mode == "release":
267
        call("vim debian/changelog")
268

    
269
    # Add changelog to INDEX
270
    repo.git.add("debian/changelog")
271
    # Commit Changes
272
    repo.git.commit("-s", "-a", m="Bump version to %s" % debian_version)
273
    # Tag debian branch
274
    debian_branch_tag = "debian/" + utils.version_to_tag(debian_version)
275
    if mode == "release":
276
        repo.git.tag(debian_branch_tag)
277

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

    
281
    # Create debian packages
282
    cd(repo_dir)
283
    version_files = []
284
    for _, pkg_info in config['packages'].items():
285
        version_files.append(pkg_info['version_file'])
286
    ignore_regexp = "|".join(["^(%s)$" % vf for vf in version_files])
287
    build_cmd = "git-buildpackage --git-export-dir=%s"\
288
                " --git-upstream-branch=%s --git-debian-branch=%s"\
289
                " --git-export=INDEX --git-ignore-new -sa"\
290
                " --source-option='\"--extend-diff-ignore=%s\"'"\
291
                " --git-upstream-tag=%s"\
292
                % (build_dir, branch, debian_branch, ignore_regexp,
293
                   upstream_tag)
294
    if options.source_only:
295
        build_cmd += " -S"
296
    if not options.sign:
297
        build_cmd += " -uc -us"
298
    elif options.keyid:
299
        build_cmd += " -k\"'%s'\"" % options.keyid
300
    call(build_cmd)
301

    
302
    # Remove cloned repo
303
    if mode != 'release' and not options.keep_repo:
304
        print_green("Removing cloned repo '%s'." % repo_dir)
305
        rm("-r", repo_dir)
306

    
307
    # Print final info
308
    info = (("Version", debian_version),
309
            ("Upstream branch", branch),
310
            ("Upstream tag", branch_tag),
311
            ("Debian branch", debian_branch),
312
            ("Debian tag", debian_branch_tag),
313
            ("Repository directory", repo_dir),
314
            ("Packages directory", build_dir))
315
    print_green("\n".join(["%s: %s" % (name, val) for name, val in info]))
316

    
317
    # Print help message
318
    if mode == "release":
319
        origin = original_repo.remote().url
320
        repo.create_remote("original_origin", origin)
321
        print_green("Created remote 'original_origin' for the repository '%s'"
322
                    % origin)
323

    
324
        print_green("To update repositories '%s' and '%s' go to '%s' and run:"
325
                    % (toplevel, origin, repo_dir))
326
        for remote in ['origin', 'original_origin']:
327
            objects = [debian_branch, branch_tag, debian_branch_tag]
328
            print_green("git push %s %s" % (remote, " ".join(objects)))
329

    
330

    
331
def create_temp_directory(suffix):
332
    create_dir_cmd = mktemp("-d", "/tmp/" + suffix + "-XXXXX")
333
    return create_dir_cmd.stdout.strip()
334

    
335

    
336
def call(cmd):
337
    rc = os.system(cmd)
338
    if rc:
339
        raise RuntimeError("Command '%s' failed!" % cmd)
340

    
341

    
342
if __name__ == "__main__":
343
    sys.exit(main())