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()) |