Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
This repository was archived by the owner on May 23, 2024. It is now read-only.

Commit 7c172f3

Browse filesBrowse files
authored
Merge pull request #48 from heroku-python/python3
Cleanup for Python 3 compat
2 parents 5f8cf4e + 00a5fc7 commit 7c172f3
Copy full SHA for 7c172f3

File tree

Expand file treeCollapse file tree

5 files changed

+54
-66
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+54
-66
lines changed

‎.travis.yml

Copy file name to clipboardExpand all lines: .travis.yml
-6Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,3 @@ script:
1414
# TODO: Replace with an actual test suite:
1515
# https://github.com/kennethreitz/bob-builder/issues/31
1616
- bob --help
17-
matrix:
18-
allow_failures:
19-
- python: "3.4"
20-
- python: "3.5"
21-
- python: "3.6"
22-
fast_finish: true

‎bob/cli.py

Copy file name to clipboardExpand all lines: bob/cli.py
+19-10Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
Configuration:
1414
Environment Variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET, S3_PREFIX (optional), UPSTREAM_S3_BUCKET (optional), UPSTREAM_S3_PREFIX (optional)
1515
"""
16-
from __future__ import print_function
17-
16+
import os
17+
import signal
1818
import sys
1919

2020
from docopt import docopt
@@ -28,7 +28,7 @@ def build(formula, name=None):
2828
try:
2929
assert f.exists
3030
except AssertionError:
31-
print_stderr("Formula {} doesn't exist.".format(formula))
31+
print_stderr("Formula {} doesn't exist.".format(formula), title='ERROR')
3232
sys.exit(1)
3333

3434
# CLI lies ahead.
@@ -40,10 +40,10 @@ def build(formula, name=None):
4040
def deploy(formula, overwrite, name):
4141
f = build(formula, name)
4242

43-
print('Archiving.')
43+
print_stderr('Archiving.')
4444
f.archive()
4545

46-
print('Deploying.')
46+
print_stderr('Deploying.')
4747
f.deploy(allow_overwrite=overwrite)
4848

4949

@@ -63,9 +63,18 @@ def main():
6363
deploy(formula, overwrite=do_overwrite, name=do_name)
6464

6565

66+
def sigint_handler(signo, frame):
67+
# when receiving a signal, a process must kill itself using the same signal
68+
# sys.exit()ing 0, 1, 130, whatever will not signal to the calling program that we terminated in response to the signal
69+
# best example: `for f in a b c; do bob deploy $f; done`, hitting Ctrl+C should interrupt Bob and stop the bash loop
70+
# that's only possible if Bash knows that we exited in response to Ctrl+C (=SIGINT), then it'll also terminate the loop
71+
# bash will report the exit status as 128+$signal, so 130 for SIGINT, but sys.exit(130) does not to the same thing - the value of 130 is simply bash's representation
72+
# killing ourselves with the signal number that we are aborting in response to does all this correctly, and bash will see the right WIFSIGNALED() status of our program, not WIFEXITED()
73+
74+
# and finally, before we send ourselves the right signal, we must first restore the handler for it to the default
75+
signal.signal(signo, signal.SIG_DFL)
76+
os.kill(os.getpid(), signo)
77+
6678
def dispatch():
67-
try:
68-
main()
69-
except KeyboardInterrupt:
70-
print('ool.')
71-
sys.exit(130)
79+
signal.signal(signal.SIGINT, sigint_handler)
80+
main()

‎bob/models.py

Copy file name to clipboardExpand all lines: bob/models.py
+29-26Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# -*- coding: utf-8 -*-
22

3-
from __future__ import print_function
4-
53
import os
64
import re
75
import shutil
6+
import signal
87
import sys
98
from tempfile import mkstemp, mkdtemp
9+
from subprocess import Popen
1010

1111
from .utils import (
1212
archive_tree, extract_tree, get_with_wildcard, iter_marker_lines, mkdir_p,
13-
pipe, print_stderr, process, S3ConnectionHandler)
13+
print_stderr, S3ConnectionHandler)
1414

1515

1616
WORKSPACE = os.environ.get('WORKSPACE_DIR', 'workspace')
@@ -29,11 +29,6 @@
2929
DEPS_MARKER = '# Build Deps: '
3030
BUILD_PATH_MARKER = '# Build Path: '
3131

32-
# Make stdin/out as unbuffered as possible via file descriptor modes.
33-
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
34-
sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
35-
36-
3732
class Formula(object):
3833

3934
def __init__(self, path, override_path=None):
@@ -42,7 +37,7 @@ def __init__(self, path, override_path=None):
4237
self.override_path = override_path
4338

4439
if not S3_BUCKET:
45-
print_stderr('The environment variable S3_BUCKET must be set to the bucket name.')
40+
print_stderr('The environment variable S3_BUCKET must be set to the bucket name.', title='ERROR')
4641
sys.exit(1)
4742

4843
s3 = S3ConnectionHandler()
@@ -95,22 +90,22 @@ def resolve_deps(self):
9590
deps = self.depends_on
9691

9792
if deps:
98-
print('Fetching dependencies... found {}:'.format(len(deps)))
93+
print_stderr('Fetching dependencies... found {}:'.format(len(deps)))
9994

10095
for dep in deps:
101-
print(' - {}'.format(dep))
96+
print_stderr(' - {}'.format(dep))
10297

10398
key_name = '{}{}.tar.gz'.format(S3_PREFIX, dep)
10499
key = get_with_wildcard(self.bucket, key_name)
105100

106101
if not key and self.upstream:
107-
print(' Not found in S3_BUCKET, trying UPSTREAM_S3_BUCKET...')
102+
print_stderr(' Not found in S3_BUCKET, trying UPSTREAM_S3_BUCKET...')
108103
key_name = '{}{}.tar.gz'.format(UPSTREAM_S3_PREFIX, dep)
109104
key = get_with_wildcard(self.upstream, key_name)
110105

111106
if not key:
112107
print_stderr('Archive {} does not exist.\n'
113-
'Please deploy it to continue.'.format(key_name))
108+
'Please deploy it to continue.'.format(key_name), title='ERROR')
114109
sys.exit(1)
115110

116111
# Grab the Dep from S3, download it to a temp file.
@@ -120,7 +115,7 @@ def resolve_deps(self):
120115
# Extract the Dep to the appropriate location.
121116
extract_tree(archive, self.build_path)
122117

123-
print()
118+
print_stderr()
124119

125120
def build(self):
126121
# Prepare build directory.
@@ -133,38 +128,46 @@ def build(self):
133128
# Temporary directory where work will be carried out, because of David.
134129
cwd_path = mkdtemp(prefix='bob-')
135130

136-
print('Building formula {} in {}:\n'.format(self.path, cwd_path))
131+
print_stderr('Building formula {} in {}:\n'.format(self.path, cwd_path))
137132

138133
# Execute the formula script.
139134
args = ["/usr/bin/env", "bash", "--", self.full_path, self.build_path]
140135
if self.override_path != None:
141136
args.append(self.override_path)
142137

143-
p = process(args, cwd=cwd_path)
138+
p = Popen(args, cwd=cwd_path, shell=False, stderr=sys.stdout.fileno()) # we have to pass sys.stdout.fileno(), because subprocess.STDOUT will not do what we want on older versions: https://bugs.python.org/issue22274
144139

145-
pipe(p.stdout, sys.stdout, indent=True)
146140
p.wait()
147141

148-
if p.returncode != 0:
149-
print_stderr('Formula exited with return code {}.'.format(p.returncode))
142+
if p.returncode > 0:
143+
print_stderr('Formula exited with return code {}.'.format(p.returncode), title='ERROR')
150144
sys.exit(1)
151-
152-
print('\nBuild complete: {}'.format(self.build_path))
145+
elif p.returncode < 0: # script was terminated by signal number abs(returncode)
146+
signum = abs(p.returncode)
147+
try:
148+
# Python 3.5+
149+
signame = signal.Signals(signum).name
150+
except AttributeError:
151+
signame = signum
152+
print_stderr('Formula terminated by signal {}.'.format(signame), title='ERROR')
153+
sys.exit(128+signum) # best we can do, given how we weren't terminated ourselves with the same signal (maybe we're PID 1, maybe another reason)
154+
155+
print_stderr('\nBuild complete: {}'.format(self.build_path))
153156

154157
def archive(self):
155158
"""Archives the build directory as a tar.gz."""
156159
archive = mkstemp(prefix='bob-build-', suffix='.tar.gz')[1]
157160
archive_tree(self.build_path, archive)
158161

159-
print('Created: {}'.format(archive))
162+
print_stderr('Created: {}'.format(archive))
160163
self.archived_path = archive
161164

162165
def deploy(self, allow_overwrite=False):
163166
"""Deploys the formula's archive to S3."""
164167
assert self.archived_path
165168

166169
if self.bucket.connection.anon:
167-
print_stderr('Deploy requires valid AWS credentials.')
170+
print_stderr('Deploy requires valid AWS credentials.', title='ERROR')
168171
sys.exit(1)
169172

170173
if self.override_path != None:
@@ -179,16 +182,16 @@ def deploy(self, allow_overwrite=False):
179182
if key:
180183
if not allow_overwrite:
181184
print_stderr('Archive {} already exists.\n'
182-
'Use the --overwrite flag to continue.'.format(key_name))
185+
'Use the --overwrite flag to continue.'.format(key_name), title='ERROR')
183186
sys.exit(1)
184187
else:
185188
key = self.bucket.new_key(key_name)
186189

187190
url = key.generate_url(0, query_auth=False)
188-
print('Uploading to: {}'.format(url))
191+
print_stderr('Uploading to: {}'.format(url))
189192

190193
# Upload the archive, set permissions.
191194
key.set_contents_from_filename(self.archived_path)
192195
key.set_acl('public-read')
193196

194-
print('Upload complete!')
197+
print_stderr('Upload complete!')

‎bob/utils.py

Copy file name to clipboardExpand all lines: bob/utils.py
+5-23Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66
import os
77
import sys
88
import tarfile
9-
from subprocess import Popen, PIPE, STDOUT
109

1110
import boto
1211
from boto.exception import NoAuthHandlerFound, S3ResponseError
1312

1413
from distutils.version import LooseVersion
1514
from fnmatch import fnmatchcase
1615

17-
def print_stderr(message, prefix='ERROR'):
18-
print('\n{}: {}\n'.format(prefix, message), file=sys.stderr)
16+
def print_stderr(message='', title=''):
17+
print(('\n{1}: {0}\n' if title else '{0}').format(message, title), file=sys.stderr)
1918

2019

2120
def iter_marker_lines(marker, formula, strip=True):
@@ -42,23 +41,6 @@ def mkdir_p(path):
4241
raise
4342

4443

45-
def process(cmd, cwd=None):
46-
"""A simple wrapper around the subprocess module; stderr is redirected to stdout."""
47-
p = Popen(cmd, cwd=cwd, shell=False, stdout=PIPE, stderr=STDOUT)
48-
return p
49-
50-
51-
def pipe(a, b, indent=True):
52-
"""Pipes stream A to stream B, with optional indentation."""
53-
54-
for line in iter(a.readline, b''):
55-
56-
if indent:
57-
b.write(' ')
58-
59-
b.write(line)
60-
61-
6244
def archive_tree(dir, archive):
6345
"""Creates a tar.gz archive from a given directory."""
6446
with tarfile.open(archive, 'w:gz') as tar:
@@ -104,16 +86,16 @@ def __init__(self):
10486
self.s3 = boto.connect_s3()
10587
except NoAuthHandlerFound:
10688
print_stderr('No AWS credentials found. Requests will be made without authentication.',
107-
prefix='WARNING')
89+
title='WARNING')
10890
self.s3 = boto.connect_s3(anon=True)
10991

11092
def get_bucket(self, name):
11193
try:
11294
return self.s3.get_bucket(name)
11395
except S3ResponseError as e:
11496
if e.status == 403 and not self.s3.anon:
115-
print('Access denied for bucket "{}" using found credentials. '
116-
'Retrying as an anonymous user.'.format(name))
97+
print_stderr('Access denied for bucket "{}" using found credentials. '
98+
'Retrying as an anonymous user.'.format(name), title='NOTICE')
11799
if not hasattr(self, 's3_anon'):
118100
self.s3_anon = boto.connect_s3(anon=True)
119101
return self.s3_anon.get_bucket(name)

‎setup.py

Copy file name to clipboardExpand all lines: setup.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name='bob-builder',
13-
version='0.0.17',
13+
version='0.0.18',
1414
install_requires=deps,
1515
description='Binary Build Toolkit.',
1616
# long_description='Meh.',/

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.