Statistics
| Branch: | Tag: | Revision:

root / devflow / autopkg.py @ f9f89675

History | View | Annotate | Download (11.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="unstable",
136
                      help="If running in snapshot mode, automatically set"
137
                           " the changelog distribution to this value"
138
                           " (default=unstable).")
139

    
140
    (options, args) = parser.parse_args()
141

    
142
    if options.help:
143
        print_help(parser.get_prog_name())
144
        parser.print_help()
145
        return
146

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

    
156
    # Load the repository
157
    original_repo = utils.get_repository()
158

    
159
    # Check that repository is clean
160
    toplevel = original_repo.working_dir
161
    if original_repo.is_dirty() and not options.force_dirty:
162
        raise RuntimeError(red("Repository %s is dirty." % toplevel))
163

    
164
    # Get packages from configuration file
165
    config = utils.get_config(options.config_file)
166
    packages = config['packages'].keys()
167
    print_green("Will build the following packages:\n" + "\n".join(packages))
168

    
169
    # Get current branch name and type and check if it is a valid one
170
    branch = original_repo.head.reference.name
171
    branch = utils.undebianize(branch)
172
    branch_type_str = utils.get_branch_type(branch)
173

    
174
    if branch_type_str not in BRANCH_TYPES.keys():
175
        allowed_branches = ", ".join(BRANCH_TYPES.keys())
176
        raise ValueError("Malformed branch name '%s', cannot classify as"
177
                         " one of %s" % (branch, allowed_branches))
178

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

    
188
    # Check that base version file and branch are correct
189
    versioning.get_python_version()
190

    
191
    # Get the debian branch
192
    debian_branch = utils.get_debian_branch(branch)
193
    origin_debian = "origin/" + debian_branch
194

    
195
    # Clone the repo
196
    repo_dir = options.repo_dir or create_temp_directory("df-repo")
197
    repo_dir = os.path.abspath(repo_dir)
198
    repo = original_repo.clone(repo_dir, branch=branch)
199
    print_green("Cloned repository to '%s'." % repo_dir)
200

    
201
    build_dir = options.build_dir or create_temp_directory("df-build")
202
    build_dir = os.path.abspath(build_dir)
203
    print_green("Build directory: '%s'" % build_dir)
204

    
205
    # Create the debian branch
206
    repo.git.branch(debian_branch, origin_debian)
207
    print_green("Created branch '%s' to track '%s'" % (debian_branch,
208
                origin_debian))
209

    
210
    # Go to debian branch
211
    repo.git.checkout(debian_branch)
212
    print_green("Changed to branch '%s'" % debian_branch)
213

    
214
    # Merge with starting branch
215
    repo.git.merge(branch)
216
    print_green("Merged branch '%s' into '%s'" % (branch, debian_branch))
217

    
218
    # Compute python and debian version
219
    cd(repo_dir)
220
    python_version = versioning.get_python_version()
221
    debian_version = versioning.\
222
        debian_version_from_python_version(python_version)
223
    print_green("The new debian version will be: '%s'" % debian_version)
224

    
225
    # Update the version files
226
    versioning.update_version()
227

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

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

    
247
    if mode == "release":
248
        call("vim debian/changelog")
249
    else:
250
        f = open("debian/changelog", 'r+')
251
        lines = f.readlines()
252
        lines[0] = lines[0].replace("UNRELEASED", options.dist)
253
        lines[2] = lines[2].replace("UNRELEASED", "Snapshot build")
254
        f.seek(0)
255
        f.writelines(lines)
256
        f.close()
257

    
258
    # Add changelog to INDEX
259
    repo.git.add("debian/changelog")
260
    # Commit Changes
261
    repo.git.commit("-s", "-a", m="Bump version to %s" % debian_version)
262
    # Tag debian branch
263
    debian_branch_tag = "debian/" + utils.version_to_tag(debian_version)
264
    if mode == "release":
265
        repo.git.tag(debian_branch_tag)
266

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

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

    
289
    # Remove cloned repo
290
    if mode != 'release' and not options.keep_repo:
291
        print_green("Removing cloned repo '%s'." % repo_dir)
292
        rm("-r", repo_dir)
293

    
294
    # Print final info
295
    info = (("Version", debian_version),
296
            ("Upstream branch", branch),
297
            ("Upstream tag", branch_tag),
298
            ("Debian branch", debian_branch),
299
            ("Debian tag", debian_branch_tag),
300
            ("Repository directory", repo_dir),
301
            ("Packages directory", build_dir))
302
    print_green("\n".join(["%s: %s" % (name, val) for name, val in info]))
303

    
304
    # Print help message
305
    if mode == "release":
306
        origin = original_repo.remote().url
307
        repo.create_remote("original_origin", origin)
308
        print_green("Created remote 'original_origin' for the repository '%s'"
309
                    % origin)
310

    
311
        print_green("To update repositories '%s' and '%s' go to '%s' and run:"
312
                    % (toplevel, origin, repo_dir))
313
        for remote in ['origin', 'original_origin']:
314
            objects = [debian_branch, branch_tag, debian_branch_tag]
315
            print_green("git push %s %s" % (remote, " ".join(objects)))
316

    
317

    
318
def create_temp_directory(suffix):
319
    create_dir_cmd = mktemp("-d", "/tmp/" + suffix + "-XXXXX")
320
    return create_dir_cmd.stdout.strip()
321

    
322

    
323
def call(cmd):
324
    rc = os.system(cmd)
325
    if rc:
326
        raise RuntimeError("Command '%s' failed!" % cmd)
327

    
328

    
329
if __name__ == "__main__":
330
    sys.exit(main())