Revision 5d3daee1

/dev/null
1
#!/bin/bash
2

  
3
REMOTEUPSTREAM=develop
4
REMOTEDEBIAN=debian-develop
5
PKGAREA=~/packages
6
BACKUPAREA=~/backup
7
BUILDAREA=$(mktemp -d --tmpdir=/tmp build-area.XXX)
8

  
9
PACKAGES="
10
  snf-quotaholder-app
11
  snf-astakos-app
12
  snf-common
13
  snf-webproject
14
  snf-cyclades-app
15
  snf-cyclades-gtools
16
  snf-tools
17
  snf-pithos-app
18
  snf-pithos-backend
19
  snf-pithos-tools"
20

  
/dev/null
1
# Copyright 2012 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
import git
35
import os
36
import sys
37
from sh import mktemp, cd, rm, git_dch, python
38
from optparse import OptionParser
39

  
40
try:
41
    from colors import red, green
42
except ImportError:
43
    red = lambda x: x
44
    green = lambda x: x
45

  
46
print_red = lambda x: sys.stdout.write(red(x) + "\n")
47
print_green = lambda x: sys.stdout.write(green(x) + "\n")
48

  
49
AVAILABLE_MODES = ["release", "snapshot"]
50
PACKAGES = (
51
  "snf-astakos-app",
52
  "snf-common",
53
  "snf-webproject",
54
  "snf-cyclades-app",
55
  "snf-cyclades-gtools",
56
  "snf-tools",
57
  "snf-pithos-app",
58
  "snf-pithos-backend",
59
  "snf-pithos-tools",
60
)
61

  
62

  
63
def main():
64
    parser = OptionParser(usage="usage: %prog [options] mode",
65
                          version="%prog 1.0")
66
    parser.add_option("-k", "--keep-repo",
67
                      action="store_true",
68
                      dest="keep_repo",
69
                      default=False,
70
                      help="Do not delete the cloned repository")
71
    parser.add_option("-b", "--build-dir",
72
                      dest="build_dir",
73
                      default=None,
74
                      help="Directory to store created pacakges")
75
    parser.add_option("-r", "--repo-dir",
76
                     dest="repo_dir",
77
                     default=None,
78
                     help="Directory to clone repository")
79
    parser.add_option("-d", "--dirty",
80
                     dest="force_dirty",
81
                     default=False,
82
                     action="store_true",
83
                     help="Do not check if working directory is dirty")
84

  
85
    (options, args) = parser.parse_args()
86

  
87
    mode = args[0]
88
    if mode not in AVAILABLE_MODES:
89
        raise ValueError(red("Invalid argument! Mode must be one: %s"
90
                         % ", ".join(AVAILABLE_MODES)))
91

  
92
    # Do not prompt for merge message. Required for some Git versions
93
    os.environ["GITFLOW_BUILD_MODE"] = mode
94

  
95
    try:
96
        original_repo = git.Repo(".")
97
    except git.git.InvalidGitRepositoryError:
98
        raise RuntimeError(red("Current directory is not git repository."))
99

  
100
    if original_repo.is_dirty() and not options.force_dirty:
101
        toplevel = original_repo.working_dir
102
        raise RuntimeError(red("Repository %s is dirty." % toplevel))
103

  
104
    repo_dir = options.repo_dir
105
    if not repo_dir:
106
        repo_dir = mktemp("-d", "/tmp/synnefo-build-repo-XXX").stdout.strip()
107
        print_green("Created temporary directory '%s' for the cloned repo."
108
                    % repo_dir)
109

  
110
    repo = original_repo.clone(repo_dir)
111
    print_green("Cloned current repository to '%s'." % repo_dir)
112

  
113
    reflog_hexsha = repo.head.log()[-1].newhexsha
114
    print "Latest Reflog entry is %s" % reflog_hexsha
115

  
116
    branch = repo.head.reference.name
117
    if branch == "master":
118
        debian_branch = "debian"
119
    else:
120
        debian_branch = "debian-" + branch
121

  
122
    try:
123
        repo.references[debian_branch]
124
    except IndexError:
125
        # Branch does not exist
126
        # FIXME: remove hard-coded strings..
127
        if branch == "debian":
128
            repo.git.branch("--track", debian_branch, "origin/debian")
129
        else:
130
            repo.git.branch("--track", debian_branch, "origin/debian-develop")
131

  
132
    repo.git.checkout(debian_branch)
133
    print_green("Changed to branch '%s'" % debian_branch)
134

  
135
    repo.git.merge(branch)
136
    print_green("Merged branch '%s' into '%s'" % (branch, debian_branch))
137

  
138
    cd(repo_dir)
139
    version = python(repo_dir + "/devtools/version.py", "debian").strip()
140
    print_green("The new debian version will be: '%s'" % version)
141

  
142
    dch = git_dch("--debian-branch=%s" % debian_branch,
143
            "--git-author",
144
            "--ignore-regex=\".*\"",
145
            "--multimaint-merge",
146
            "--since=HEAD",
147
            "--new-version=%s" % version)
148
    print_green("Successfully ran '%s'" % " ".join(dch.cmd))
149

  
150
    os.system("vim debian/changelog")
151
    repo.git.add("debian/changelog")
152

  
153
    if mode == "release":
154
        repo.git.commit("-s", "-a", "-m", "Bump new upstream version")
155
        if branch == "master":
156
            repo.git.tag("debian/" + version)
157

  
158
    for package in PACKAGES:
159
        # python setup.py should run in its directory
160
        cd(package)
161
        package_dir = repo_dir + "/" + package
162
        res = python(package_dir + "/setup.py", "sdist", _out=sys.stdout)
163
        cd("../")
164
        print res.stdout
165

  
166
    # Add version.py files to repo
167
    os.system("grep \"__version_vcs\" -r . -l -I | xargs git add -f")
168

  
169
    build_dir = options.build_dir
170
    if not options.build_dir:
171
        build_dir = mktemp("-d", "/tmp/synnefo-build-XXX").stdout.strip()
172
        print_green("Created directory '%s' to store the .deb files." %
173
                     build_dir)
174

  
175
    os.system("git-buildpackage --git-export-dir=%s --git-upstream-branch=%s"
176
              " --git-debian-branch=%s --git-export=INDEX --git-ignore-new -sa"
177
              % (build_dir, branch, debian_branch))
178

  
179
    if not options.keep_repo:
180
        print_green("Removing cloned repo '%s'." % repo_dir)
181
        rm("-r", repo_dir)
182
    else:
183
        print_green("Repository dir '%s'" % repo_dir)
184

  
185
    print_green("Completed. Version '%s', build area: '%s'"
186
                % (version, build_dir))
187

  
188

  
189
if __name__ == "__main__":
190
    main()
/dev/null
1
#!/bin/bash
2

  
3
# Copyright 2012 GRNET S.A. All rights reserved.
4
# 
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
# 
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
# 
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
# 
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
# 
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

  
36
parse_git_branch()
37
{
38
    git branch 2> /dev/null | grep '^*' | sed 's/^*\ //g'
39
}
40

  
41
die()
42
{
43
    echo $* 1>&2
44
    exit 1
45
}
46

  
47
cleanup()
48
{
49
    trap - EXIT
50

  
51
    if [ ${#CLEANUP[*]} -gt 0 ]; then
52
        LAST_ELEMENT=$((${#CLEANUP[*]}-1))
53
        REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
54
        for i in $REVERSE_INDEXES; do
55
            local cmd=${CLEANUP[$i]}
56
            $cmd
57
        done
58
    fi
59
}
60

  
61
add_cleanup() {
62
    local cmd=""
63
    for arg; do cmd+=$(printf "%q " "$arg"); done
64
    CLEANUP+=("$cmd")
65
}
66

  
67

  
68
add_checkpoint()
69
{
70
    commit=$(git reflog | head -n1 | cut -f 1 -d " ")
71
    add_cleanup git reset --hard $commit
72
    LASTCHECKPOINT=$commit
73
}
74

  
75
CLEANUP=( )
76

  
77
source devtools/autopkg.conf
78

  
79
# The root of the git repository, no matter where we're called from
80
TOPLEVEL="$(git rev-parse --show-toplevel)"
81
CURRENT_BRANCH=$(parse_git_branch)
82

  
83
LOCALBRANCH="$CURRENT_BRANCH"
84
LOCALDEBIAN=$1
85
DEBIANBRANCH=${LOCALDEBIAN:- origin/$REMOTEDEBIAN}
86

  
87
MODIFIED=$(git status --short | grep -v "??")
88
if [[ -n $MODIFIED ]]; then
89
        echo "error: Repository is dirty. Commit your local changes."
90
        exit 1
91
fi
92

  
93

  
94
set -e
95
trap cleanup EXIT
96

  
97
cd "$TOPLEVEL"
98

  
99
# Prerequisites: Test all important directories exist
100
test -d "$PKGAREA" || die "Package area directory $PKGAREA missing"
101
test -d "$BACKUPAREA" || die "Backup area directory $BACKUPAREA missing"
102

  
103
# Prerequisite: Test the dialog utility is available
104
dialog --help &>/dev/null || die "Could not run the 'dialog' utility"
105

  
106

  
107
echo "##########################################################"
108
echo "Will build packages"
109
echo "under '$BUILDAREA',"
110
echo "from local branch '$LOCALBRANCH'"
111
echo "and debian branch '$DEBIANBRANCH'"
112
echo "##########################################################"
113
echo "Press Enter to continue..."
114
read
115

  
116
add_checkpoint
117

  
118
# Create a temporary debian branch to do everything
119
TMPDEBIAN=$(mktemp -u debian.XXX)
120

  
121
git branch --track $TMPDEBIAN  $DEBIANBRANCH
122
#add_cleanup git branch -D $TMPDEBIAN
123

  
124
git checkout $TMPDEBIAN
125
add_cleanup git checkout $LOCALBRANCH
126

  
127
add_checkpoint
128

  
129
# Whether we are in snapshot or release mode
130
snap=false
131
mrgextra=-m
132
dchextra=-R
133
mrgmsg="Merge branch '$REMOTEUPSTREAM' into $REMOTEDEBIAN"
134
dialog --yesno "Create Snapshot?" 5 20 && snap=true && GITFLOW_BUILD_MODE=snapshot && dchextra=-S && mrgextra= && mrgmsg=
135

  
136
# merge local branch into tmp branch with a nice commit message,
137
# so it can be pushed as is to upstream debian
138
GIT_MERGE_AUTOEDIT=no
139
git merge $mrgextra ${mrgextra:+"$mrgmsg"} $LOCALBRANCH
140

  
141
# auto edit Debian changelog depending on Snapshot or Release mode
142
export EDITOR=/usr/bin/vim
143

  
144
# use the devtools to determine Debian version
145
export GITFLOW_BUILD_MODE
146
version=$(devtools/version.py debian)
147
git-dch --debian-branch=$TMPDEBIAN --git-author --ignore-regex=".*" --multimaint-merge --since=HEAD -N $version
148
git add debian/changelog
149

  
150
# get version from the changelog
151
# we add a git tag here, so setup.py sdist works as expected
152
# FIXME: This is a workaround for the way Synnefo packages determine
153
#        the versions for their Python packages
154
version=$(IFS="()" ; read  x v x < debian/changelog  ; echo $v)
155
if ! $snap; then
156
  git commit -s -a -m "Bump new upstream version"
157
  TAGFILE=$(mktemp -t tag.XXX)
158
  add_cleanup rm $TAGFILE
159
  dialog --inputbox "New Debian Tag: " 5 30 "debian/$version" 2>$TAGFILE
160
  git tag $(<$TAGFILE)
161
  add_cleanup git tag -d $(<$TAGFILE)
162
fi
163

  
164

  
165
for p in $PACKAGES; do
166

  
167
  cd  $p
168
  python setup.py sdist
169
  grep "__version_vcs" -r . -l -I | xargs git add -f
170
  cd -
171

  
172
done
173

  
174
# Build all packages
175
git-buildpackage --git-export-dir="$BUILDAREA" \
176
                 --git-upstream-branch=$LOCALBRANCH \
177
                 --git-debian-branch=$TMPDEBIAN \
178
                 --git-export=INDEX \
179
                 --git-ignore-new -sa
180

  
181
# do some dirty backup
182
# pkgarea might be needed by auto-deploy tool
183
rm -f "$PKGAREA"/* || true
184
cp -v "$BUILDAREA"/* "$PKGAREA"/ || true
185
cp -v "$BUILDAREA"/* "$BACKUPAREA"/ || true
186

  
187
echo "###############################################"
188
echo "####              SUCCESS                  ####"
189
echo "###############################################"
190

  
191
git fetch origin
192
#check if your local branch is up-to-date
193
commits_behind=$(git rev-list $LOCALBRANCH..origin/$REMOTEUPSTREAM | wc -l)
194
if [ $commits_behind -ne 0 ]; then
195
  die "Your local branch is outdated!! Please run: git pull --rebase origin/$REMOTEUPSTREAM"
196
fi
197
commits_behind=$(git rev-list $DEBIANBRANCH..origin/$REMOTEDEBIAN | wc -l)
198
if [ $commits_behind -ne 0 ]; then
199
  die "Your debian branch is outdated!! Please run: git pull --rebase origin/$REMOTEDEBIAN"
200
fi
201

  
202
trap - EXIT
203

  
204
# Remove the added versions.py files
205
git reset --hard HEAD
206
# here we can push the commits to the remote debian branch as they are
207

  
208
if ! $snap; then
209
  TAGS="--tags"
210
fi
211
echo "git push $TAGS origin $TMPDEBIAN:$REMOTEDEBIAN"
212
echo "git checkout $LOCALBRANCH"
213
echo "git push $TAGS origin $LOCALBRANCH:$REMOTEUPSTREAM"
214
echo
215

  
216
exit 0
/dev/null
1
#!/usr/bin/env python
2
#
3
# Copyright 2012 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35
#
36
#
37

  
38
"""Unit Tests for devtools.version
39

  
40
Provides unit tests for module devtools.version,
41
for automatic generation of version strings.
42

  
43
"""
44

  
45
import os
46
import unittest
47
from pkg_resources import parse_version
48
from version import debian_version_from_python_version
49

  
50

  
51
class DebianVersionObject(object):
52
    """Object representing a Debian Version."""
53
    def __init__(self, pyver):
54
        self.version = debian_version_from_python_version(pyver)
55

  
56
    def __str__(self):
57
        return self.version
58

  
59

  
60
def debian_compare_versions(a, op, b):
61
    i = os.system("dpkg --compare-versions %s %s %s" % (a, op, b))
62
    return i == 0
63

  
64
# Set ordering between DebianVersionObject objects, by adding
65
# debian_compare_versions
66
for op in ["lt", "le", "eq", "ne", "gt", "ge"]:
67
    def gen(op):
68
        def operator_func(self, other):
69
            return debian_compare_versions(self.version, op, other.version)
70
        return operator_func
71
    setattr(DebianVersionObject, "__%s__" % op, gen(op))
72

  
73

  
74
def _random_commit():
75
    import random
76
    import string
77
    return "".join(random.choice(string.hexdigits) for n in xrange(8)).lower()
78

  
79

  
80
# Add a random commit number at the end of snapshot versions
81
def version_with_commit(parse_func, v):
82
    if "_" in v:
83
        return parse_func(v + "_" + _random_commit())
84
    else:
85
        return parse_func(v)
86

  
87
V = lambda v: version_with_commit(parse_version, v)
88
D = lambda v: version_with_commit(DebianVersionObject, v)
89

  
90

  
91
class TestVersionFunctions(unittest.TestCase):
92
    def setUp(self):
93
        self.version_orderings = (
94
            ("0.14next", ">", "0.14"),
95
            ("0.14next", ">", "0.14rc7"),
96
            ("0.14next", "<", "0.14.1"),
97
            ("0.14rc6", "<", "0.14"),
98
            ("0.14.2rc6", ">", "0.14.1"),
99
            ("0.14next_150", "<", "0.14next"),
100
            ("0.14.1next_150", "<", "0.14.1next"),
101
            ("0.14.1_149", "<", "0.14.1"),
102
            ("0.14.1_149", "<", "0.14.1_150"),
103
            ("0.13next_102", "<", "0.13next"),
104
            ("0.13next", "<", "0.14rc5_120"),
105
            ("0.14rc3_120", "<", "0.14rc3"),
106
            # The following test fails, but version.python_version
107
            # will never try to produce such a version:
108
            # ("0.14rc3", "<", "0.14_1"),
109
            ("0.14_120", "<", "0.14"),
110
            ("0.14", "<", "0.14next_20"),
111
            ("0.14next_20", "<", "0.14next"),
112
        )
113

  
114
    def test_python_versions(self):
115
        for a, op, b in self.version_orderings:
116
            res = compare(V, a, op, b)
117
            self.assertTrue(res, "Python version: %s %s %s"
118
                                 " is not True" % (a, op, b))
119

  
120
    def test_debian_versions(self):
121
        for a, op, b in self.version_orderings:
122
            res = compare(D, a, op, b)
123
            self.assertTrue(res, "Debian version %s %s %s"
124
                                 " is not True" % (a, op, b))
125

  
126

  
127
def compare(function, a, op, b):
128
    import operator
129
    str_to_op = {"<": operator.lt,
130
            "<=": operator.le,
131
            "==": operator.eq,
132
            ">": operator.gt,
133
            ">=": operator.ge}
134
    try:
135
        return str_to_op[op](function(a), function(b))
136
    except KeyError:
137
        raise ValueError("Unknown operator '%s'" % op)
138

  
139
if __name__ == '__main__':
140
    unittest.main()
/dev/null
1
# Copyright 2011 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

  
35
import os
36
import sys
37

  
38
from contextlib import contextmanager
39
from fabric.api import *
40
from fabric.colors import *
41

  
42
env.project_root = "./"
43
env.develop = False
44
env.autoremove = True
45

  
46
env.packages = ['snf-common', 'snf-cyclades-app', 'snf-cyclades-gtools',
47
                'snf-webproject', 'snf-pithos-backend', 'snf-pithos-app',
48
                'snf-pithos-tools', 'snf-astakos-app']
49

  
50
env.capture = False
51
env.colors = True
52
env.pypi_root = 'pypi'
53
env.roledefs = {
54
    'docs': ['docs.dev.grnet.gr'],
55
    'pypi': ['docs.dev.grnet.gr']
56
}
57

  
58

  
59
# colored logging
60
notice = lambda x: sys.stdout.write(yellow(x) + "\n")
61
info = lambda x: sys.stdout.write(green(x) + "\n")
62
error = lambda x: sys.stdout.write(red(x) + "\n")
63

  
64

  
65
def dev():
66
    env.develop = True
67

  
68

  
69
# wrap local to respect global capturing setting from env.capture
70
oldlocal = local
71

  
72

  
73
def local(cmd, capture="default"):
74
    if capture != "default":
75
        capture = capture
76
    else:
77
        capture = env.capture
78
    return oldlocal(cmd, capture=capture)
79

  
80

  
81
def package_root(p):
82
    return os.path.join(env.project_root, p)
83

  
84

  
85
def remove_pkg(p):
86
    notice("uninstalling package: %s" % p)
87
    with lcd(package_root(p)):
88
        with settings(warn_only=True):
89
            local("pip uninstall %s -y" % p, env.capture)
90

  
91

  
92
def build_pkg(p):
93
    info("building package: %s" % p)
94
    with lcd(package_root(p)):
95
        local("if [ -d dist ]; then rm -r dist; fi;")
96
        local("if [ -d build ]; then rm -r build; fi;")
97
        local("python setup.py egg_info -d sdist")
98

  
99

  
100
def install_pkg(p):
101
    info("installing package: %s" % p)
102
    with lcd(package_root(p)):
103
        if env.develop:
104
            local("python setup.py develop")
105
        else:
106
            local("python setup.py install")
107

  
108

  
109
def install(*packages):
110
    for p in packages:
111
        install_pkg("snf-%s" % p)
112

  
113

  
114
def buildall():
115
    for p in env.packages:
116
        build_pkg(p)
117
    collectdists()
118

  
119

  
120
def installall():
121
    for p in env.packages:
122
        install_pkg(p)
123

  
124

  
125
def collectdists():
126
    if os.path.exists("./packages"):
127
        notice("removing 'packages' directory")
128
        local("rm -r packages")
129

  
130
    local("mkdir packages")
131
    for p in env.packages:
132
        local("cp %s/dist/*.tar.gz ./packages/" % package_root(p))
133

  
134

  
135
def removeall():
136
    for p in env.packages:
137
        remove_pkg(p)
138

  
139

  
140
def remove(*packages):
141
    for p in packages:
142
        remove_pkg("snf-%s" % p)
143

  
144

  
145
#
146
# GIT helpers
147
#
148

  
149

  
150
def git(params, locl=True):
151
    cmd = local if locl else run
152
    return cmd("git %s" % params, capture=True)
153

  
154

  
155
def branch():
156
    return git("symbolic-ref HEAD").split("/")[-1]
157

  
158

  
159
@contextmanager
160
def co(c):
161
    current_branch = branch()
162
    git("checkout %s" % c)
163
    # Use a try block to make sure we checkout the original branch.
164
    try:
165
        yield
166
    finally:
167
        try:
168
            git("checkout %s" % current_branch)
169
        except Exception:
170
            error("Could not checkout %s, you're still left at %s" % c)
171

  
172
#
173
# Debian packaging helpers
174
#
175

  
176
env.debian_branch = 'debian'
177
env.deb_packages = ['snf-common', 'snf-cyclades-app', 'snf-cyclades-gtools',
178
                    'snf-webproject', 'snf-pithos-backend', 'snf-pithos-tools',
179
                    'snf-pithos-app', 'snf-astakos-app']
180
env.signdebs = True
181
env.debrelease = False  # Increase release number in Debian changelogs
182

  
183
def _last_commit(f):
184
    return local("git rev-list --all --date-order --max-count=1 %s" % f,
185
                 capture=True).strip()
186

  
187

  
188
def _diff_from_master(c,f):
189
    return local("git log --oneline %s..master %s" \
190
                 " | wc -l" % (c, f), capture=True)
191

  
192

  
193
def dch(p):
194
    with co(env.debian_branch):
195
        local("git merge master")
196
        local("git merge %s" % env.upstream)
197
        with lcd(package_root(p)):
198
            local("if [ ! -d .git ]; then mkdir .git; fi")
199

  
200
            # FIXME:
201
            # Checking for new changes in packages
202
            # has been removed temporarily.
203
            # Always create a new Debian changelog entry.
204
            ## Check for new changes in package dir
205
            #diff = _diff_from_master(_last_commit("debian/changelog"), ".")
206
            #vercmd  = "git describe --tags --abbrev=0"\
207
            #          " | sed -rn '\''s/^v(.*)/\\1/p'\''"
208
            #version = local(vercmd, capture=True)
209
            #if int(diff) > 0:
210
            if True:
211
                # Run git-dch in snapshot mode.
212
                # TODO: Support a --release mode in fabfile
213
                if not env.debrelease:
214
                    notice(("Producing snapshot changelog entry, "
215
                            "use 'debrelease' to produce release entries."))
216
                local(("git-dch --debian-branch=%s --auto %s" %
217
                       (env.debian_branch,
218
                        "--release" if env.debrelease else "--snapshot")))
219
                local(("git commit debian/changelog"
220
                       " -m 'Updated %s changelog'" % p))
221
                notice(("Make sure to tag Debian release in %s" %
222
                        env.debian_branch))
223

  
224
            local("rmdir .git")
225

  
226

  
227
def dchall():
228
    for p in env.deb_packages:
229
        info("updating debian changelog for package: %s" % p)
230
        dch(p)
231

  
232

  
233
def debrelease():
234
    env.debrelease = True
235

  
236

  
237
def signdebs():
238
    env.signdebs = True
239

  
240

  
241
# Commands which automatically add and reset the version files which are not tracked by
242
# git. Those version files are created from each setup.py using the synnefo-common
243
# update_version, so we execute `python setup.py clean` to ensure that file gets
244
# created and git add will not fail. The reset of those files after each build
245
# certifies that succeded git checkouts will not fail due to existing local
246
# changes.
247
add_versions_cmd = "find . -regextype posix-egrep -regex \".*version.py$|.*\/synnefo\/versions\/.*py$\" -exec git add -f {} \;"
248
reset_versions_cmd = "find . -regextype posix-egrep -regex \".*version.py$|.*\/synnefo\/versions\/.*py$\" -exec git reset {} \;"
249

  
250

  
251
def builddeb(p, master="master", branch="debian-0.8"):
252
    with co(branch):
253
        info("Building debian package for %s" % p)
254
        with lcd(package_root(p)):
255
            local("git merge master")
256
            local("if [ ! -d .git ]; then mkdir .git; fi")
257
            local("python setup.py clean")
258
            local(add_versions_cmd)
259
            local(("git-buildpackage --git-upstream-branch=%s --git-debian-branch=%s"
260
                   " --git-export=INDEX --git-ignore-new %s") %
261
                  (master, branch, "" if env.signdebs else "-us -uc"))
262
            local("rm -rf .git")
263
            local(reset_versions_cmd)
264
        info("Done building debian package for %s" % p)
265

  
266

  
267
def builddeball(b="debian-0.8"):
268
    for p in env.deb_packages:
269
        builddeb(p=p, branch=b)
270

  
271

  
272
@roles('pypi')
273
def uploadtars():
274
    put("packages/*.tar.gz", 'www/pypi/')
275

  
276

  
277
def cleandocs():
278
    """
279
    Remove _build directories for each doc project
280
    """
281

  
282
    # docs contains conf.py in root directory
283
    if os.path.exists("docs/docs/_build"):
284
        local("rm -r docs/docs/_build")
285

  
286
    for p in env.packages:
287
        buildpth = os.path.join(package_root(p), 'docs', '_build')
288
        if os.path.exists(buildpth):
289
            local('rm -r %s' % buildpth)
290

  
291

  
292
def builddocs():
293
    """
294
    Run sphinx builder for each project separately
295
    """
296
    builddocs_cmd = "sphinx-build -b html -d _build/doctrees   . _build/html"
297

  
298
    # docs contains conf.py in root directory
299
    with lcd("docs"):
300
        local(builddocs_cmd)
301

  
302
    for p in env.packages:
303
        info("Building %s docs" % p)
304
        docspth = os.path.join(package_root(p), 'docs')
305
        if os.path.exists(docspth):
306
            with lcd(docspth):
307
                local(builddocs_cmd)
/dev/null
1
#!/bin/bash
2
#
3
#
4
# Copyright 2011 GRNET S.A. All rights reserved.
5
#
6
# Redistribution and use in source and binary forms, with or
7
# without modification, are permitted provided that the following
8
# conditions are met:
9
#
10
#   1. Redistributions of source code must retain the above
11
#      copyright notice, this list of conditions and the following
12
#      disclaimer.
13
#
14
#   2. Redistributions in binary form must reproduce the above
15
#      copyright notice, this list of conditions and the following
16
#      disclaimer in the documentation and/or other materials
17
#      provided with the distribution.
18
#
19
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
20
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
23
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
# POSSIBILITY OF SUCH DAMAGE.
31
#
32
# The views and conclusions contained in the software and
33
# documentation are those of the authors and should not be
34
# interpreted as representing official policies, either expressed
35
# or implied, of GRNET S.A.
36
#
37

  
38
set -e
39

  
40
rm -rf env
41
virtualenv --no-site-packages -ppython2.6 env
42
source env/bin/activate
43
export PIP_DOWNLOAD_CACHE=/tmp/.pip_cache
44
pip install -r requirements.pip
45

  
46
cd snf-common
47
rm -rf build dist
48
python setup.py install
49
cd ../snf-cyclades-app
50
rm -rf build dist
51
python setup.py install
52
cd ../snf-cyclades-gtools
53
rm -rf build dist
54
python setup.py install
55

  
56

  
57
cd ../env
58
# avoid vncauthproxy errors
59
rm bin/vncauthproxy.py
60
echo "running django tests..." >&2
61
export SYNNEFO_SETTINGS_DIR=/etc/lala
62
snf-manage test admin api db logic userdata --settings=synnefo.settings.test
63
cd ..
64
deactivate
65

  
66
#rm -rf env
67
#virtualenv --no-site-packages -ppython2.7 env
68
#source env/bin/activate
69
#export PIP_DOWNLOAD_CACHE=/tmp/.pip_cache
70
#pip install -r requirements.pip
71

  
72
#cd snf-common
73
#rm -rf build dist
74
#python setup.py install
75
#cd ../snf-cyclades-app
76
#rm -rf build dist
77
#python setup.py install
78
#cd ../snf-cyclades-gtools
79
#rm -rf build dist
80
#python setup.py install
81

  
82
#cd env
83
## avoid vncauthproxy errors
84
#rm bin/vncauthproxy.py
85
#echo "running django tests..." >&2
86
#snf-manage test aai admin api db helpdesk invitations logic userdata --settings=synnefo.settings.test
87
#cd ..
88
#deactivate
89
#rm -rf env
/dev/null
1
#!/bin/bash
2
#
3
#
4
# Copyright 2011 GRNET S.A. All rights reserved.
5
#
6
# Redistribution and use in source and binary forms, with or
7
# without modification, are permitted provided that the following
8
# conditions are met:
9
#
10
#   1. Redistributions of source code must retain the above
11
#      copyright notice, this list of conditions and the following
12
#      disclaimer.
13
#
14
#   2. Redistributions in binary form must reproduce the above
15
#      copyright notice, this list of conditions and the following
16
#      disclaimer in the documentation and/or other materials
17
#      provided with the distribution.
18
#
19
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
20
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
23
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
# POSSIBILITY OF SUCH DAMAGE.
31
#
32
# The views and conclusions contained in the software and
33
# documentation are those of the authors and should not be
34
# interpreted as representing official policies, either expressed
35
# or implied, of GRNET S.A.
36
#
37

  
38
set -e
39

  
40
echo "Running snf-cyclades-app tests..." >&2
41
snf-manage test admin api db logic userdata --settings=synnefo.settings.test
42

  
43
echo "Running snf-cyclades-gtools tests..." >&2
44
./snf-cyclades-gtools/test/synnefo.ganeti_unittest.py
45

  
/dev/null
1
#!/usr/bin/env python
2
#
3
# Copyright (C) 2010, 2011, 2012 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

  
36

  
37
import os
38
import re
39
import sys
40
import pprint
41
import subprocess
42
import git
43

  
44
from distutils import log
45
from collections import namedtuple
46

  
47

  
48
# Branch types:
49
# builds_snapshot: Whether the branch can produce snapshot builds
50
# builds_release: Whether the branch can produce release builds
51
# versioned: Whether the name of the branch defines a specific version
52
# allowed_version_re: A regular expression describing allowed values for
53
#                     base_version in this branch
54
branch_type = namedtuple("branch_type", ["builds_snapshot", "builds_release",
55
                                         "versioned", "allowed_version_re"])
56
VERSION_RE = "[0-9]+\.[0-9]+"
57
BRANCH_TYPES = {
58
    "feature": branch_type(True, False, False, "^%snext$" % VERSION_RE),
59
    "develop": branch_type(True, False, False, "^%snext$" % VERSION_RE),
60
    "release": branch_type(True, True, True,
61
                           "^(?P<bverstr>%s)rc[1-9][0-9]*$" % VERSION_RE),
62
    "master": branch_type(False, True, False,
63
                          "^%s$" % VERSION_RE),
64
    "hotfix": branch_type(True, True, True,
65
                          "^(?P<bverstr>^%s\.[1-9][0-9]*)$" % VERSION_RE)}
66
BASE_VERSION_FILE = "version"
67

  
68

  
69
def get_commit_id(commit, current_branch):
70
    """Return the commit ID
71

  
72
    If the commit is a 'merge' commit, and one of the parents is a
73
    debian branch we return a compination of the parents commits.
74

  
75
    """
76
    def short_id(commit):
77
        return commit.hexsha[0:7]
78

  
79
    parents = commit.parents
80
    cur_br_name = current_branch.name
81
    if len(parents) == 1:
82
        return short_id(commit)
83
    elif len(parents) == 2:
84
        if cur_br_name.startswith("debian-") or cur_br_name == "debian":
85
            pr1, pr2 = parents
86
            return short_id(pr1) + "~" + short_id(pr2)
87
        else:
88
            return short_id(commit)
89
    else:
90
        raise RuntimeError("Commit %s has more than 2 parents!" % commit)
91

  
92

  
93
def vcs_info():
94
    """
95
    Return current git HEAD commit information.
96

  
97
    Returns a tuple containing
98
        - branch name
99
        - commit id
100
        - commit count
101
        - git describe output
102
        - path of git toplevel directory
103

  
104
    """
105
    try:
106
        repo = git.Repo(".")
107
        branch = repo.head.reference
108
        revid = get_commit_id(branch.commit, branch)
109
        revno = len(list(repo.iter_commits()))
110
        desc = repo.git.describe("--tags")
111
        toplevel = repo.working_dir
112
    except git.InvalidGitRepositoryError:
113
        log.error("Could not retrieve git information. " +
114
                  "Current directory not a git repository?")
115
        return None
116

  
117
    info = namedtuple("vcs_info", ["branch", "revid", "revno",
118
                                   "desc", "toplevel"])
119

  
120
    return info(branch=branch.name, revid=revid, revno=revno, desc=desc,
121
                toplevel=toplevel)
122

  
123

  
124
def base_version(vcs_info):
125
    """Determine the base version from a file in the repository"""
126

  
127
    f = open(os.path.join(vcs_info.toplevel, BASE_VERSION_FILE))
128
    lines = [l.strip() for l in f.readlines()]
129
    l = [l for l in lines if not l.startswith("#")]
130
    if len(l) != 1:
131
        raise ValueError("File '%s' should contain a single non-comment line.")
132
    return l[0]
133

  
134

  
135
def build_mode():
136
    """Determine the build mode from the value of $GITFLOW_BUILD_MODE"""
137
    try:
138
        mode = os.environ["GITFLOW_BUILD_MODE"]
139
        assert mode == "release" or mode == "snapshot"
140
    except (KeyError, AssertionError):
141
        raise ValueError("GITFLOW_BUILD_MODE environment variable must be "
142
                         "'release' or 'snapshot'")
143
    return mode
144

  
145

  
146
def python_version(base_version, vcs_info, mode):
147
    """Generate a Python distribution version following devtools conventions.
148

  
149
    This helper generates a Python distribution version from a repository
150
    commit, following devtools conventions. The input data are:
151
        * base_version: a base version number, presumably stored in text file
152
          inside the repository, e.g., /version
153
        * vcs_info: vcs information: current branch name and revision no
154
        * mode: "snapshot", or "release"
155

  
156
    This helper assumes a git branching model following:
157
    http://nvie.com/posts/a-successful-git-branching-model/
158

  
159
    with 'master', 'develop', 'release-X', 'hotfix-X' and 'feature-X' branches.
160

  
161
    General rules:
162
    a) any repository commit can get as a Python version
163
    b) a version is generated either in 'release' or in 'snapshot' mode
164
    c) the choice of mode depends on the branch, see following table.
165

  
166
    A python version is of the form A_NNN,
167
    where A: X.Y.Z{,next,rcW} and NNN: a revision number for the commit,
168
    as returned by vcs_info().
169

  
170
    For every combination of branch and mode, releases are numbered as follows:
171

  
172
    BRANCH:  /  MODE: snapshot        release
173
    --------          ------------------------------
174
    feature           0.14next_150    N/A
175
    develop           0.14next_151    N/A
176
    release           0.14rc2_249     0.14rc2
177
    master            N/A             0.14
178
    hotfix            0.14.1rc6_121   0.14.1rc6
179
                      N/A             0.14.1
180

  
181
    The suffix 'next' in a version name is used to denote the upcoming version,
182
    the one being under development in the develop and release branches.
183
    Version '0.14next' is the version following 0.14, and only lives on the
184
    develop and feature branches.
185

  
186
    The suffix 'rc' is used to denote release candidates. 'rc' versions live
187
    only in release and hotfix branches.
188

  
189
    Suffixes 'next' and 'rc' have been chosen to ensure proper ordering
190
    according to setuptools rules:
191

  
192
        http://www.python.org/dev/peps/pep-0386/#setuptools
193

  
194
    Every branch uses a value for A so that all releases are ordered based
195
    on the branch they came from, so:
196

  
197
    So
198
        0.13next < 0.14rcW < 0.14 < 0.14next < 0.14.1
199

  
200
    and
201

  
202
    >>> V("0.14next") > V("0.14")
203
    True
204
    >>> V("0.14next") > V("0.14rc7")
205
    True
206
    >>> V("0.14next") > V("0.14.1")
207
    False
208
    >>> V("0.14rc6") > V("0.14")
209
    False
210
    >>> V("0.14.2rc6") > V("0.14.1")
211
    True
212

  
213
    The value for _NNN is chosen based of the revision number of the specific
214
    commit. It is used to ensure ascending ordering of consecutive releases
215
    from the same branch. Every version of the form A_NNN comes *before*
216
    than A: All snapshots are ordered so they come before the corresponding
217
    release.
218

  
219
    So
220
        0.14next_* < 0.14
221
        0.14.1_* < 0.14.1
222
        etc
223

  
224
    and
225

  
226
    >>> V("0.14next_150") < V("0.14next")
227
    True
228
    >>> V("0.14.1next_150") < V("0.14.1next")
229
    True
230
    >>> V("0.14.1_149") < V("0.14.1")
231
    True
232
    >>> V("0.14.1_149") < V("0.14.1_150")
233
    True
234

  
235
    Combining both of the above, we get
236
       0.13next_* < 0.13next < 0.14rcW_* < 0.14rcW < 0.14_* < 0.14
237
       < 0.14next_* < 0.14next < 0.14.1_* < 0.14.1
238

  
239
    and
240

  
241
    >>> V("0.13next_102") < V("0.13next")
242
    True
243
    >>> V("0.13next") < V("0.14rc5_120")
244
    True
245
    >>> V("0.14rc3_120") < V("0.14rc3")
246
    True
247
    >>> V("0.14rc3") < V("0.14_1")
248
    True
249
    >>> V("0.14_120") < V("0.14")
250
    True
251
    >>> V("0.14") < V("0.14next_20")
252
    True
253
    >>> V("0.14next_20") < V("0.14next")
254
    True
255

  
256
    Note: one of the tests above fails because of constraints in the way
257
    setuptools parses version numbers. It does not affect us because the
258
    specific version format that triggers the problem is not contained in the
259
    table showing allowed branch / mode combinations, above.
260

  
261

  
262
    """
263

  
264
    branch = vcs_info.branch
265

  
266
    # If it's a debian branch, ignore starting "debian-"
267
    brnorm = branch
268
    if brnorm == "debian":
269
        brnorm = "debian-master"
270
    if brnorm.startswith("debian-"):
271
        brnorm = brnorm.replace("debian-", "", 1)
272

  
273
    # Sanity checks
274
    if "-" in brnorm:
275
        btypestr = brnorm.split("-")[0]
276
    else:
277
        btypestr = brnorm
278

  
279
    try:
280
        btype = BRANCH_TYPES[btypestr]
281
    except KeyError:
282
        allowed_branches = ", ".join(x for x in BRANCH_TYPES.keys())
283
        raise ValueError("Malformed branch name '%s', cannot classify as one "
284
                         "of %s" % (btypestr, allowed_branches))
285

  
286
    if btype.versioned:
287
        try:
288
            bverstr = brnorm.split("-")[1]
289
        except IndexError:
290
            # No version
291
            raise ValueError("Branch name '%s' should contain version" %
292
                             branch)
293

  
294
        # Check that version is well-formed
295
        if not re.match(VERSION_RE, bverstr):
296
            raise ValueError("Malformed version '%s' in branch name '%s'" %
297
                             (bverstr, branch))
298

  
299
    m = re.match(btype.allowed_version_re, base_version)
300
    if not m or (btype.versioned and m.groupdict()["bverstr"] != bverstr):
301
        raise ValueError("Base version '%s' unsuitable for branch name '%s'" %
302
                         (base_version, branch))
303

  
304
    if mode not in ["snapshot", "release"]:
305
        raise ValueError("Specified mode '%s' should be one of 'snapshot' or "
306
                         "'release'" % mode)
307
    snap = (mode == "snapshot")
308

  
309
    if ((snap and not btype.builds_snapshot) or
310
        (not snap and not btype.builds_release)):
311
        raise ValueError("Invalid mode '%s' in branch type '%s'" %
312
                         (mode, btypestr))
313

  
314
    if snap:
315
        v = "%s_%d_%s" % (base_version, vcs_info.revno, vcs_info.revid)
316
    else:
317
        v = base_version
318
    return v
319

  
320

  
321
def debian_version_from_python_version(pyver):
322
    """Generate a debian package version from a Python version.
323

  
324
    This helper generates a Debian package version from a Python version,
325
    following devtools conventions.
326

  
327
    Debian sorts version strings differently compared to setuptools:
328
    http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
329

  
330
    Initial tests:
331

  
332
    >>> debian_version("3") < debian_version("6")
333
    True
334
    >>> debian_version("3") < debian_version("2")
335
    False
336
    >>> debian_version("1") == debian_version("1")
337
    True
338
    >>> debian_version("1") != debian_version("1")
339
    False
340
    >>> debian_version("1") >= debian_version("1")
341
    True
342
    >>> debian_version("1") <= debian_version("1")
343
    True
344

  
345
    This helper defines a 1-1 mapping between Python and Debian versions,
346
    with the same ordering.
347

  
348
    Debian versions are ordered in the same way as Python versions:
349

  
350
    >>> D("0.14next") > D("0.14")
351
    True
352
    >>> D("0.14next") > D("0.14rc7")
353
    True
354
    >>> D("0.14next") > D("0.14.1")
355
    False
356
    >>> D("0.14rc6") > D("0.14")
357
    False
358
    >>> D("0.14.2rc6") > D("0.14.1")
359
    True
360

  
361
    and
362

  
363
    >>> D("0.14next_150") < D("0.14next")
364
    True
365
    >>> D("0.14.1next_150") < D("0.14.1next")
366
    True
367
    >>> D("0.14.1_149") < D("0.14.1")
368
    True
369
    >>> D("0.14.1_149") < D("0.14.1_150")
370
    True
371

  
372
    and
373

  
374
    >>> D("0.13next_102") < D("0.13next")
375
    True
376
    >>> D("0.13next") < D("0.14rc5_120")
377
    True
378
    >>> D("0.14rc3_120") < D("0.14rc3")
379
    True
380
    >>> D("0.14rc3") < D("0.14_1")
381
    True
382
    >>> D("0.14_120") < D("0.14")
383
    True
384
    >>> D("0.14") < D("0.14next_20")
385
    True
386
    >>> D("0.14next_20") < D("0.14next")
387
    True
388

  
389
    """
390
    return pyver.replace("_", "~").replace("rc", "~rc") + "-1"
391

  
392

  
393
def debian_version(base_version, vcs_info, mode):
394
    p = python_version(base_version, vcs_info, mode)
395
    return debian_version_from_python_version(p)
396

  
397

  
398
def user_info():
399
    import getpass
400
    import socket
401
    return "%s@%s" % (getpass.getuser(), socket.getfqdn())
402

  
403

  
404
def update_version(module, name="version", root="."):
405
    """
406
    Generate or replace version.py as a submodule of `module`.
407

  
408
    This is a helper to generate/replace a version.py file containing version
409
    information as a submodule of passed `module`.
410

  
411
    """
412

  
413
    v = vcs_info()
414
    if not v:
415
        # Return early if not in development environment
416
        return
417
    b = base_version(v)
418
    mode = build_mode()
419
    paths = [root] + module.split(".") + ["%s.py" % name]
420
    module_filename = os.path.join(*paths)
421
    version = python_version(b, v, mode)
422
    content = """
423
__version__ = "%(version)s"
424
__version_info__ = %(version_info)s
425
__version_vcs_info__ = %(vcs_info)s
426
__version_user_info__ = "%(user_info)s"
427
    """ % dict(version=version, version_info=version.split("."),
428
               vcs_info=pprint.PrettyPrinter().pformat(dict(v._asdict())),
429
               user_info=user_info())
430

  
431
    module_file = file(module_filename, "w+")
432
    module_file.write(content)
433
    module_file.close()
434

  
435

  
436
if __name__ == "__main__":
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff