root / devflow / autopkg.py @ 06edfd12
History | View | Annotate | Download (13.3 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 |
parser.add_option("--push-back",
|
149 |
dest="push_back",
|
150 |
default=False,
|
151 |
action="store_true",
|
152 |
help="Automatically push branches and tags to repo.")
|
153 |
|
154 |
(options, args) = parser.parse_args() |
155 |
|
156 |
if options.help:
|
157 |
print_help(parser.get_prog_name()) |
158 |
parser.print_help() |
159 |
return
|
160 |
|
161 |
# Get build mode
|
162 |
try:
|
163 |
mode = args[0]
|
164 |
except IndexError: |
165 |
mode = utils.get_build_mode() |
166 |
if mode not in AVAILABLE_MODES: |
167 |
raise ValueError(red("Invalid argument! Mode must be one: %s" |
168 |
% ", ".join(AVAILABLE_MODES)))
|
169 |
|
170 |
# Load the repository
|
171 |
original_repo = utils.get_repository() |
172 |
|
173 |
# Check that repository is clean
|
174 |
toplevel = original_repo.working_dir |
175 |
if original_repo.is_dirty() and not options.force_dirty: |
176 |
raise RuntimeError(red("Repository %s is dirty." % toplevel)) |
177 |
|
178 |
# Get packages from configuration file
|
179 |
config = utils.get_config(options.config_file) |
180 |
packages = config['packages'].keys()
|
181 |
print_green("Will build the following packages:\n" + "\n".join(packages)) |
182 |
|
183 |
# Get current branch name and type and check if it is a valid one
|
184 |
branch = original_repo.head.reference.name |
185 |
branch = utils.undebianize(branch) |
186 |
branch_type_str = utils.get_branch_type(branch) |
187 |
|
188 |
if branch_type_str not in BRANCH_TYPES.keys(): |
189 |
allowed_branches = ", ".join(BRANCH_TYPES.keys())
|
190 |
raise ValueError("Malformed branch name '%s', cannot classify as" |
191 |
" one of %s" % (branch, allowed_branches))
|
192 |
|
193 |
# Fix needed environment variables
|
194 |
v = utils.get_vcs_info() |
195 |
os.environ["DEVFLOW_BUILD_MODE"] = mode
|
196 |
os.environ["DEBFULLNAME"] = v.name
|
197 |
os.environ["DEBEMAIL"] = v.email
|
198 |
|
199 |
# Check that base version file and branch are correct
|
200 |
versioning.get_python_version() |
201 |
|
202 |
# Get the debian branch
|
203 |
if options.debian_branch:
|
204 |
debian_branch = options.debian_branch |
205 |
else:
|
206 |
debian_branch = utils.get_debian_branch(branch) |
207 |
origin_debian = "origin/" + debian_branch
|
208 |
|
209 |
# Clone the repo
|
210 |
repo_dir = options.repo_dir or create_temp_directory("df-repo") |
211 |
repo_dir = os.path.abspath(repo_dir) |
212 |
repo = original_repo.clone(repo_dir, branch=branch) |
213 |
print_green("Cloned repository to '%s'." % repo_dir)
|
214 |
|
215 |
build_dir = options.build_dir or create_temp_directory("df-build") |
216 |
build_dir = os.path.abspath(build_dir) |
217 |
print_green("Build directory: '%s'" % build_dir)
|
218 |
|
219 |
# Create the debian branch
|
220 |
repo.git.branch(debian_branch, origin_debian) |
221 |
print_green("Created branch '%s' to track '%s'" % (debian_branch,
|
222 |
origin_debian)) |
223 |
|
224 |
# Go to debian branch
|
225 |
repo.git.checkout(debian_branch) |
226 |
print_green("Changed to branch '%s'" % debian_branch)
|
227 |
|
228 |
# Merge with starting branch
|
229 |
repo.git.merge(branch) |
230 |
print_green("Merged branch '%s' into '%s'" % (branch, debian_branch))
|
231 |
|
232 |
# Compute python and debian version
|
233 |
cd(repo_dir) |
234 |
python_version = versioning.get_python_version() |
235 |
debian_version = versioning.\ |
236 |
debian_version_from_python_version(python_version) |
237 |
print_green("The new debian version will be: '%s'" % debian_version)
|
238 |
|
239 |
# Update the version files
|
240 |
versioning.update_version() |
241 |
|
242 |
if not options.sign: |
243 |
sign_tag_opt = None
|
244 |
elif options.keyid:
|
245 |
sign_tag_opt = "-u=%s" % options.keyid
|
246 |
elif mode == "release": |
247 |
sign_tag_opt = "-s"
|
248 |
else:
|
249 |
sign_tag_opt = None
|
250 |
|
251 |
# Tag branch with python version
|
252 |
branch_tag = python_version |
253 |
tag_message = "%s version %s" % (mode.capitalize(), python_version)
|
254 |
try:
|
255 |
repo.git.tag(branch_tag, branch, sign_tag_opt, "-m %s" % tag_message)
|
256 |
except GitCommandError:
|
257 |
# Tag may already exist, if only the debian branch has changed
|
258 |
pass
|
259 |
upstream_tag = "upstream/" + branch_tag
|
260 |
repo.git.tag(upstream_tag, branch) |
261 |
|
262 |
# Update changelog
|
263 |
dch = git_dch("--debian-branch=%s" % debian_branch,
|
264 |
"--git-author",
|
265 |
"--ignore-regex=\".*\"",
|
266 |
"--multimaint-merge",
|
267 |
"--since=HEAD",
|
268 |
"--new-version=%s" % debian_version)
|
269 |
print_green("Successfully ran '%s'" % " ".join(dch.cmd)) |
270 |
|
271 |
if options.dist is not None: |
272 |
distribution = options.dist |
273 |
elif mode == "release": |
274 |
distribution = utils.get_distribution_codename() |
275 |
else:
|
276 |
distribution = "unstable"
|
277 |
|
278 |
f = open("debian/changelog", 'r+') |
279 |
lines = f.readlines() |
280 |
lines[0] = lines[0].replace("UNRELEASED", distribution) |
281 |
lines[2] = lines[2].replace("UNRELEASED", "%s build" % mode) |
282 |
f.seek(0)
|
283 |
f.writelines(lines) |
284 |
f.close() |
285 |
|
286 |
if mode == "release": |
287 |
call("vim debian/changelog")
|
288 |
|
289 |
# Add changelog to INDEX
|
290 |
repo.git.add("debian/changelog")
|
291 |
# Commit Changes
|
292 |
repo.git.commit("-s", "debian/changelog", |
293 |
m="Bump version to %s" % debian_version)
|
294 |
# Tag debian branch
|
295 |
debian_branch_tag = "debian/" + utils.version_to_tag(debian_version)
|
296 |
tag_message = "%s version %s" % (mode.capitalize(), debian_version)
|
297 |
if mode == "release": |
298 |
repo.git.tag(debian_branch_tag, sign_tag_opt, "-m %s" % tag_message)
|
299 |
|
300 |
# Add version.py files to repo
|
301 |
call("grep \"__version_vcs\" -r . -l -I | xargs git add -f")
|
302 |
|
303 |
# Create debian packages
|
304 |
cd(repo_dir) |
305 |
version_files = [] |
306 |
for _, pkg_info in config['packages'].items(): |
307 |
version_files.append(pkg_info['version_file'])
|
308 |
# Export version info to debuilg environment
|
309 |
os.environ["DEB_DEVFLOW_DEBIAN_VERSION"] = debian_version
|
310 |
os.environ["DEB_DEVFLOW_VERSION"] = python_version
|
311 |
build_cmd = "git-buildpackage --git-export-dir=%s"\
|
312 |
" --git-upstream-branch=%s --git-debian-branch=%s"\
|
313 |
" --git-export=INDEX --git-ignore-new -sa"\
|
314 |
" --source-option=--auto-commit"\
|
315 |
" --git-upstream-tag=%s"\
|
316 |
% (build_dir, branch, debian_branch, upstream_tag) |
317 |
if options.source_only:
|
318 |
build_cmd += " -S"
|
319 |
if not options.sign: |
320 |
build_cmd += " -uc -us"
|
321 |
elif options.keyid:
|
322 |
build_cmd += " -k\"'%s'\"" % options.keyid
|
323 |
call(build_cmd) |
324 |
|
325 |
# Remove cloned repo
|
326 |
if mode != 'release' and not options.keep_repo: |
327 |
print_green("Removing cloned repo '%s'." % repo_dir)
|
328 |
rm("-r", repo_dir)
|
329 |
|
330 |
# Print final info
|
331 |
info = (("Version", debian_version),
|
332 |
("Upstream branch", branch),
|
333 |
("Upstream tag", branch_tag),
|
334 |
("Debian branch", debian_branch),
|
335 |
("Debian tag", debian_branch_tag),
|
336 |
("Repository directory", repo_dir),
|
337 |
("Packages directory", build_dir))
|
338 |
print_green("\n".join(["%s: %s" % (name, val) for name, val in info])) |
339 |
|
340 |
# Print help message
|
341 |
if mode == "release": |
342 |
origin = original_repo.remote().url |
343 |
repo.create_remote("original_origin", origin)
|
344 |
print_green("Created remote 'original_origin' for the repository '%s'"
|
345 |
% origin) |
346 |
|
347 |
print_green("To update repositories '%s' and '%s' go to '%s' and run:"
|
348 |
% (toplevel, origin, repo_dir)) |
349 |
for remote in ['origin', 'original_origin']: |
350 |
objects = [debian_branch, branch_tag, debian_branch_tag] |
351 |
print_green("git push %s %s" % (remote, " ".join(objects))) |
352 |
if options.push_back:
|
353 |
objects = [debian_branch, branch_tag, debian_branch_tag] |
354 |
repo.git.push("origin", *objects)
|
355 |
print_green("Automatically updated origin repo.")
|
356 |
|
357 |
|
358 |
def create_temp_directory(suffix): |
359 |
create_dir_cmd = mktemp("-d", "/tmp/" + suffix + "-XXXXX") |
360 |
return create_dir_cmd.stdout.strip()
|
361 |
|
362 |
|
363 |
def call(cmd): |
364 |
rc = os.system(cmd) |
365 |
if rc:
|
366 |
raise RuntimeError("Command '%s' failed!" % cmd) |
367 |
|
368 |
|
369 |
if __name__ == "__main__": |
370 |
sys.exit(main()) |