Statistics
| Branch: | Tag: | Revision:

root / devflow / autopkg.py @ 1296e42b

History | View | Annotate | Download (12.5 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
    os.environ["DEVFLOW_BUILD_MODE"] = mode
190
    git_config = original_repo.config_reader()
191
    try:
192
        os.environ["DEBFULLNAME"] = git_config.get_value("user", "name")
193
        os.environ["DEBEMAIL"] = git_config.get_value("user", "email")
194
    except:
195
        print "Could not load user/email from config"
196

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

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

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

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

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

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

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

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

    
237
    # Update the version files
238
    versioning.update_version()
239

    
240
    # Tag branch with python version
241
    branch_tag = python_version
242
    try:
243
        repo.git.tag(branch_tag, branch)
244
    except GitCommandError:
245
        # Tag may already exist, if only the debian branch has changed
246
        pass
247
    upstream_tag = "upstream/" + branch_tag
248
    repo.git.tag(upstream_tag, branch)
249

    
250
    # Update changelog
251
    dch = git_dch("--debian-branch=%s" % debian_branch,
252
                  "--git-author",
253
                  "--ignore-regex=\".*\"",
254
                  "--multimaint-merge",
255
                  "--since=HEAD",
256
                  "--new-version=%s" % debian_version)
257
    print_green("Successfully ran '%s'" % " ".join(dch.cmd))
258

    
259
    if options.dist is not None:
260
        distribution = options.dist
261
    elif mode == "release":
262
        distribution = utils.get_distribution_codename()
263
    else:
264
        distribution = "unstable"
265

    
266
    f = open("debian/changelog", 'r+')
267
    lines = f.readlines()
268
    lines[0] = lines[0].replace("UNRELEASED", distribution)
269
    lines[2] = lines[2].replace("UNRELEASED", "%s build" % mode)
270
    f.seek(0)
271
    f.writelines(lines)
272
    f.close()
273

    
274
    if mode == "release":
275
        call("vim debian/changelog")
276

    
277
    # Add changelog to INDEX
278
    repo.git.add("debian/changelog")
279
    # Commit Changes
280
    repo.git.commit("-s", "-a", m="Bump version to %s" % debian_version)
281
    # Tag debian branch
282
    debian_branch_tag = "debian/" + utils.version_to_tag(debian_version)
283
    if mode == "release":
284
        repo.git.tag(debian_branch_tag)
285

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

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

    
310
    # Remove cloned repo
311
    if mode != 'release' and not options.keep_repo:
312
        print_green("Removing cloned repo '%s'." % repo_dir)
313
        rm("-r", repo_dir)
314

    
315
    # Print final info
316
    info = (("Version", debian_version),
317
            ("Upstream branch", branch),
318
            ("Upstream tag", branch_tag),
319
            ("Debian branch", debian_branch),
320
            ("Debian tag", debian_branch_tag),
321
            ("Repository directory", repo_dir),
322
            ("Packages directory", build_dir))
323
    print_green("\n".join(["%s: %s" % (name, val) for name, val in info]))
324

    
325
    # Print help message
326
    if mode == "release":
327
        origin = original_repo.remote().url
328
        repo.create_remote("original_origin", origin)
329
        print_green("Created remote 'original_origin' for the repository '%s'"
330
                    % origin)
331

    
332
        print_green("To update repositories '%s' and '%s' go to '%s' and run:"
333
                    % (toplevel, origin, repo_dir))
334
        for remote in ['origin', 'original_origin']:
335
            objects = [debian_branch, branch_tag, debian_branch_tag]
336
            print_green("git push %s %s" % (remote, " ".join(objects)))
337

    
338

    
339
def create_temp_directory(suffix):
340
    create_dir_cmd = mktemp("-d", "/tmp/" + suffix + "-XXXXX")
341
    return create_dir_cmd.stdout.strip()
342

    
343

    
344
def call(cmd):
345
    rc = os.system(cmd)
346
    if rc:
347
        raise RuntimeError("Command '%s' failed!" % cmd)
348

    
349

    
350
if __name__ == "__main__":
351
    sys.exit(main())