Statistics
| Branch: | Tag: | Revision:

root / devflow / autopkg.py @ a09634f8

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="unstable",
136
                      help="If running in snapshot mode, automatically set"
137
                           " the changelog distribution to this value"
138
                           " (default=unstable).")
139
    parser.add_option("-S", "--source-only",
140
                      dest="source_only",
141
                      default=False,
142
                      action="store_true",
143
                      help="Specifies a source-only build, no binary packages"
144
                           " need to be made.")
145

    
146
    (options, args) = parser.parse_args()
147

    
148
    if options.help:
149
        print_help(parser.get_prog_name())
150
        parser.print_help()
151
        return
152

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

    
162
    # Load the repository
163
    original_repo = utils.get_repository()
164

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

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

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

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

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

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

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

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

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

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

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

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

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

    
231
    # Update the version files
232
    versioning.update_version()
233

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

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

    
253
    if mode == "release":
254
        call("vim debian/changelog")
255
    else:
256
        f = open("debian/changelog", 'r+')
257
        lines = f.readlines()
258
        lines[0] = lines[0].replace("UNRELEASED", options.dist)
259
        lines[2] = lines[2].replace("UNRELEASED", "Snapshot build")
260
        f.seek(0)
261
        f.writelines(lines)
262
        f.close()
263

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

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

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

    
297
    # Remove cloned repo
298
    if mode != 'release' and not options.keep_repo:
299
        print_green("Removing cloned repo '%s'." % repo_dir)
300
        rm("-r", repo_dir)
301

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

    
312
    # Print help message
313
    if mode == "release":
314
        origin = original_repo.remote().url
315
        repo.create_remote("original_origin", origin)
316
        print_green("Created remote 'original_origin' for the repository '%s'"
317
                    % origin)
318

    
319
        print_green("To update repositories '%s' and '%s' go to '%s' and run:"
320
                    % (toplevel, origin, repo_dir))
321
        for remote in ['origin', 'original_origin']:
322
            objects = [debian_branch, branch_tag, debian_branch_tag]
323
            print_green("git push %s %s" % (remote, " ".join(objects)))
324

    
325

    
326
def create_temp_directory(suffix):
327
    create_dir_cmd = mktemp("-d", "/tmp/" + suffix + "-XXXXX")
328
    return create_dir_cmd.stdout.strip()
329

    
330

    
331
def call(cmd):
332
    rc = os.system(cmd)
333
    if rc:
334
        raise RuntimeError("Command '%s' failed!" % cmd)
335

    
336

    
337
if __name__ == "__main__":
338
    sys.exit(main())