1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
|
#!/usr/bin/python
import os, sys, argparse, random
from shell import shell, ssh
description="Build release packages for pyqtgraph."
epilog = """
Package build is done in several steps:
* Attempt to clone branch release-x.y.z from source-repo
* Merge release branch into master
* Write new version numbers into the source
* Roll over unreleased CHANGELOG entries
* Commit and tag new release
* Build HTML documentation
* Build source package
* Build deb packages (if running on Linux)
* Build Windows exe installers
Release packages may be published by using the --publish flag:
* Uploads release files to website
* Pushes tagged git commit to github
* Uploads source package to pypi
Building source packages requires:
*
*
* python-sphinx
Building deb packages requires several dependencies:
* build-essential
* python-all, python3-all
* python-stdeb, python3-stdeb
Note: building windows .exe files should be possible on any OS. However,
Debian/Ubuntu systems do not include the necessary wininst*.exe files; these
must be manually copied from the Python source to the distutils/command
submodule path (/usr/lib/pythonX.X/distutils/command). Additionally, it may be
necessary to rename (or copy / link) wininst-9.0-amd64.exe to
wininst-6.0-amd64.exe.
"""
path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
build_dir = os.path.join(path, 'release-build')
pkg_dir = os.path.join(path, 'release-packages')
ap = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('version', help='The x.y.z version to generate release packages for. '
'There must be a corresponding pyqtgraph-x.y.z branch in the source repository.')
ap.add_argument('--publish', metavar='', help='Publish previously built package files (must be stored in pkg-dir/version) and tagged release commit (from build-dir).', action='store_const', const=True, default=False)
ap.add_argument('--source-repo', metavar='', help='Repository from which release and master branches will be cloned. Default is the repo containing this script.', default=path)
ap.add_argument('--build-dir', metavar='', help='Directory where packages will be staged and built. Default is source_root/release-build.', default=build_dir)
ap.add_argument('--pkg-dir', metavar='', help='Directory where packages will be stored. Default is source_root/release-packages.', default=pkg_dir)
ap.add_argument('--skip-pip-test', metavar='', help='Skip testing pip install.', action='store_const', const=True, default=False)
ap.add_argument('--no-deb', metavar='', help='Skip building Debian packages.', action='store_const', const=True, default=False)
ap.add_argument('--no-exe', metavar='', help='Skip building Windows exe installers.', action='store_const', const=True, default=False)
def build(args):
if os.path.exists(args.build_dir):
sys.stderr.write("Please remove the build directory %s before proceeding, or specify a different path with --build-dir.\n" % args.build_dir)
sys.exit(-1)
if os.path.exists(args.pkg_dir):
sys.stderr.write("Please remove the package directory %s before proceeding, or specify a different path with --pkg-dir.\n" % args.pkg_dir)
sys.exit(-1)
# Clone source repository and tag the release branch
shell('''
# Clone and merge release branch into previous master
mkdir -p {build_dir}
cd {build_dir}
rm -rf pyqtgraph
git clone --depth 1 --branch master --single-branch {source_repo} pyqtgraph
cd pyqtgraph
git checkout -b release-{version}
git pull {source_repo} release-{version}
git checkout master
git merge --no-ff --no-commit release-{version}
# Write new version number into the source
sed -i "s/__version__ = .*/__version__ = '{version}'/" pyqtgraph/__init__.py
sed -i "s/version = .*/version = '{version}'/" doc/source/conf.py
sed -i "s/release = .*/release = '{version}'/" doc/source/conf.py
# make sure changelog mentions unreleased changes
grep "pyqtgraph-{version}.*unreleased.*" CHANGELOG
sed -i "s/pyqtgraph-{version}.*unreleased.*/pyqtgraph-{version}/" CHANGELOG
# Commit and tag new release
git commit -a -m "PyQtGraph release {version}"
git tag pyqtgraph-{version}
# Build HTML documentation
cd doc
make clean
make html
cd ..
find ./ -name "*.pyc" -delete
# package source distribution
python setup.py sdist
mkdir -p {pkg_dir}
cp dist/*.tar.gz {pkg_dir}
# source package build complete.
'''.format(**args.__dict__))
if args.skip_pip_test:
args.pip_test = 'skipped'
else:
shell('''
# test pip install source distribution
rm -rf release-{version}-virtenv
virtualenv --system-site-packages release-{version}-virtenv
. release-{version}-virtenv/bin/activate
echo "PATH: $PATH"
echo "ENV: $VIRTUAL_ENV"
pip install --no-index --no-deps dist/pyqtgraph-{version}.tar.gz
deactivate
# pip install test passed
'''.format(**args.__dict__))
args.pip_test = 'passed'
if 'linux' in sys.platform and not args.no_deb:
shell('''
# build deb packages
cd {build_dir}/pyqtgraph
python setup.py --command-packages=stdeb.command sdist_dsc
cd deb_dist/pyqtgraph-{version}
sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control
dpkg-buildpackage
cd ../../
mv deb_dist {pkg_dir}/pyqtgraph-{version}-deb
# deb package build complete.
'''.format(**args.__dict__))
args.deb_status = 'built'
else:
args.deb_status = 'skipped'
if not args.no_exe:
shell("""
# Build windows executables
cd {build_dir}/pyqtgraph
python setup.py build bdist_wininst --plat-name=win32
python setup.py build bdist_wininst --plat-name=win-amd64
cp dist/*.exe {pkg_dir}
""".format(**args.__dict__))
args.exe_status = 'built'
else:
args.exe_status = 'skipped'
print(unindent("""
======== Build complete. =========
* Source package: built
* Pip install test: {pip_test}
* Debian packages: {deb_status}
* Windows installers: {exe_status}
* Package files in {pkg_dir}
Next steps to publish:
* Test all packages
* Run script again with --publish
""").format(**args.__dict__))
def publish(args):
if not os.path.isfile(os.path.expanduser('~/.pypirc')):
print(unindent("""
Missing ~/.pypirc file. Should look like:
-----------------------------------------
[distutils]
index-servers =
pypi
[pypi]
username:your_username
password:your_password
"""))
sys.exit(-1)
### Upload everything to server
shell("""
cd {build_dir}/pyqtgraph
# Uploading documentation.. (disabled; now hosted by readthedocs.io)
#rsync -rv doc/build/* pyqtgraph.org:/www/code/pyqtgraph/pyqtgraph/documentation/build/
# Uploading release packages to website
rsync -v {pkg_dir} pyqtgraph.org:/www/code/pyqtgraph/downloads/
# Push master to github
git push https://github.com/pyqtgraph/pyqtgraph master:master
# Push tag to github
git push https://github.com/pyqtgraph/pyqtgraph pyqtgraph-{version}
# Upload to pypi..
python setup.py sdist upload
""".format(**args.__dict__))
print(unindent("""
======== Upload complete. =========
Next steps to publish:
- update website
- mailing list announcement
- new conda recipe (http://conda.pydata.org/docs/build.html)
- contact deb maintainer (gianfranco costamagna)
- other package maintainers?
""").format(**args.__dict__))
def unindent(msg):
ind = 1e6
lines = msg.split('\n')
for line in lines:
if len(line.strip()) == 0:
continue
ind = min(ind, len(line) - len(line.lstrip()))
return '\n'.join([line[ind:] for line in lines])
if __name__ == '__main__':
args = ap.parse_args()
args.build_dir = os.path.abspath(args.build_dir)
args.pkg_dir = os.path.join(os.path.abspath(args.pkg_dir), args.version)
if args.publish:
publish(args)
else:
build(args)
|