Merge ~chad.smith/cloud-init:pyver-fix into cloud-init:master

Proposed by Chad Smith
Status: Merged
Approved by: Scott Moser
Approved revision: a4f5cf8dfc587b59662af3754146d85798263292
Merged at revision: e7c95208451422cfd3da7edfbd67dd271e7aa337
Proposed branch: ~chad.smith/cloud-init:pyver-fix
Merge into: cloud-init:master
Diff against target: 485 lines (+209/-123)
8 files modified
Makefile (+8/-1)
packages/bddeb (+11/-32)
packages/brpm (+12/-28)
packages/debian/control.in (+2/-9)
packages/pkg-deps.json (+1/-0)
packages/redhat/cloud-init.spec.in (+6/-15)
packages/suse/cloud-init.spec.in (+3/-12)
tools/read-dependencies (+166/-26)
Reviewer Review Type Date Requested Status
Scott Moser Approve
Server Team CI bot continuous-integration Needs Fixing
Review via email: mp+325283@code.launchpad.net

Description of the change

makefile: fix python 2/3 detection in the Makefile

Fix detection of python2 in a non-python3 environment.
The previous version ahead ended up using python 3.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

I'm going to grab this.
your request included a bunch of unrelated stuff, so I'll just cherry pick the Makefile commit.
thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/Makefile b/Makefile
2index 09cd147..1cec9ae 100644
3--- a/Makefile
4+++ b/Makefile
5@@ -1,6 +1,6 @@
6 CWD=$(shell pwd)
7 PYVER ?= $(shell for p in python3 python2; do \
8- out=$(which $$p 2>&1) && echo $$p && exit; done; \
9+ out=$$(command -v $$p 2>&1) && echo $$p && exit; done; \
10 exit 1)
11 noseopts ?= -v
12
13@@ -53,6 +53,12 @@ unittest: clean_pyc
14 unittest3: clean_pyc
15 nosetests3 $(noseopts) tests/unittests
16
17+ci-deps-ubuntu:
18+ @$(PYVER) $(CWD)/tools/read-dependencies --distro ubuntu --install
19+
20+ci-deps-centos:
21+ @$(PYVER) $(CWD)/tools/read-dependencies --distro centos --install
22+
23 pip-requirements:
24 @echo "Installing cloud-init dependencies..."
25 $(PIP_INSTALL) -r "$@.txt" -q
26@@ -75,6 +81,7 @@ clean_pyc:
27 clean: clean_pyc
28 rm -rf /var/log/cloud-init.log /var/lib/cloud/
29
30+
31 yaml:
32 @$(PYVER) $(CWD)/tools/validate-yaml.py $(YAML_FILES)
33
34diff --git a/packages/bddeb b/packages/bddeb
35index f415209..e45af6e 100755
36--- a/packages/bddeb
37+++ b/packages/bddeb
38@@ -24,19 +24,6 @@ if "avoid-pep8-E402-import-not-top-of-file":
39 from cloudinit import templater
40 from cloudinit import util
41
42-# Package names that will showup in requires which have unique package names.
43-# Format is '<pypi-name>': {'<python_major_version>': <pkg_name_or_none>, ...}.
44-NONSTD_NAMED_PACKAGES = {
45- 'argparse': {'2': 'python-argparse', '3': None},
46- 'contextlib2': {'2': 'python-contextlib2', '3': None},
47- 'cheetah': {'2': 'python-cheetah', '3': None},
48- 'pyserial': {'2': 'python-serial', '3': 'python3-serial'},
49- 'pyyaml': {'2': 'python-yaml', '3': 'python3-yaml'},
50- 'six': {'2': 'python-six', '3': 'python3-six'},
51- 'pep8': {'2': 'pep8', '3': 'python3-pep8'},
52- 'pyflakes': {'2': 'pyflakes', '3': 'pyflakes'},
53-}
54-
55 DEBUILD_ARGS = ["-S", "-d"]
56
57
58@@ -59,7 +46,6 @@ def write_debian_folder(root, templ_data, is_python2, cloud_util_deps):
59 else:
60 pyver = "3"
61 python = "python3"
62- pkgfmt = "{}-{}"
63
64 deb_dir = util.abs_join(root, 'debian')
65
66@@ -74,30 +60,23 @@ def write_debian_folder(root, templ_data, is_python2, cloud_util_deps):
67 params=templ_data)
68
69 # Write out the control file template
70- reqs = run_helper('read-dependencies').splitlines()
71+ reqs_output = run_helper(
72+ 'read-dependencies',
73+ args=['--distro', 'debian', '--python-version', pyver])
74+ reqs = reqs_output.splitlines()
75 test_reqs = run_helper(
76- 'read-dependencies', ['test-requirements.txt']).splitlines()
77-
78- pypi_pkgs = [p.lower().strip() for p in reqs]
79- pypi_test_pkgs = [p.lower().strip() for p in test_reqs]
80+ 'read-dependencies',
81+ ['--requirements-file', 'test-requirements.txt',
82+ '--system-pkg-names', '--python-version', pyver]).splitlines()
83
84- # Map to known packages
85 requires = ['cloud-utils | cloud-guest-utils'] if cloud_util_deps else []
86- test_requires = []
87- lists = ((pypi_pkgs, requires), (pypi_test_pkgs, test_requires))
88- for pypilist, target in lists:
89- for p in pypilist:
90- if p in NONSTD_NAMED_PACKAGES:
91- if NONSTD_NAMED_PACKAGES[p][pyver]:
92- target.append(NONSTD_NAMED_PACKAGES[p][pyver])
93- else: # Then standard package prefix
94- target.append(pkgfmt.format(python, p))
95-
96+ # We consolidate all deps as Build-Depends as our package build runs all
97+ # tests so we need all runtime dependencies anyway.
98+ requires.extend(reqs + test_reqs + [python])
99 templater.render_to_file(util.abs_join(find_root(),
100 'packages', 'debian', 'control.in'),
101 util.abs_join(deb_dir, 'control'),
102- params={'requires': ','.join(requires),
103- 'test_requires': ','.join(test_requires),
104+ params={'build_depends': ','.join(requires),
105 'python': python})
106
107 templater.render_to_file(util.abs_join(find_root(),
108diff --git a/packages/brpm b/packages/brpm
109index 89696ab..b8e7c72 100755
110--- a/packages/brpm
111+++ b/packages/brpm
112@@ -27,17 +27,6 @@ if "avoid-pep8-E402-import-not-top-of-file":
113 from cloudinit import templater
114 from cloudinit import util
115
116-# Map python requirements to package names. If a match isn't found
117-# here, we assume 'python-<pypi_name>'.
118-PACKAGE_MAP = {
119- 'redhat': {
120- 'pyserial': 'pyserial',
121- 'pyyaml': 'PyYAML',
122- },
123- 'suse': {
124- 'pyyaml': 'python-yaml',
125- }
126-}
127
128 # Subdirectories of the ~/rpmbuild dir
129 RPM_BUILD_SUBDIRS = ['BUILD', 'RPMS', 'SOURCES', 'SPECS', 'SRPMS']
130@@ -57,19 +46,15 @@ def read_dependencies():
131 '''Returns the Python depedencies from requirements.txt. This explicitly
132 removes 'argparse' from the list of requirements for python >= 2.7,
133 because with 2.7 argparse became part of the standard library.'''
134- stdout = run_helper('read-dependencies')
135- return [p.lower().strip() for p in stdout.splitlines()
136- if p != 'argparse' or (p == 'argparse' and
137- sys.version_info[0:2] < (2, 7))]
138-
139-
140-def translate_dependencies(deps, distro):
141- '''Maps python requirements into package names. We assume
142- python-<pypi_name> for packages not listed explicitly in
143- PACKAGE_MAP.'''
144- return [PACKAGE_MAP[distro][req]
145- if req in PACKAGE_MAP[distro] else 'python-%s' % req
146- for req in deps]
147+ pkg_deps = run_helper(
148+ 'read-dependencies', args=['--distro', 'redhat']).splitlines()
149+ test_deps = run_helper(
150+ 'read-dependencies', args=[
151+ '--requirements-file', 'test-requirements.txt',
152+ '--system-pkg-names']).splitlines()
153+ return [pkg_dep for pkg_dep in pkg_deps + test_deps
154+ if p != 'python-argparse' or (p == 'python-argparse' and
155+ sys.version_info[0:2] < (2, 7))]
156
157
158 def read_version():
159@@ -99,10 +84,9 @@ def generate_spec_contents(args, version_data, tmpl_fn, top_dir, arc_fn):
160 rpm_upstream_version = version_data['version']
161 subs['rpm_upstream_version'] = rpm_upstream_version
162
163- # Map to known packages
164- python_deps = read_dependencies()
165- package_deps = translate_dependencies(python_deps, args.distro)
166- subs['requires'] = package_deps
167+ deps = read_dependencies()
168+ subs['buildrequires'] = deps
169+ subs['requires'] = deps
170
171 if args.boot == 'sysvinit':
172 subs['sysvinit'] = True
173diff --git a/packages/debian/control.in b/packages/debian/control.in
174index 6c39d53..265b261 100644
175--- a/packages/debian/control.in
176+++ b/packages/debian/control.in
177@@ -3,20 +3,13 @@ Source: cloud-init
178 Section: admin
179 Priority: optional
180 Maintainer: Scott Moser <smoser@ubuntu.com>
181-Build-Depends: debhelper (>= 9),
182- dh-python,
183- dh-systemd,
184- ${python},
185- ${test_requires},
186- ${requires}
187+Build-Depends: ${build_depends}
188 XS-Python-Version: all
189 Standards-Version: 3.9.6
190
191 Package: cloud-init
192 Architecture: all
193-Depends: procps,
194- ${python},
195- ${misc:Depends},
196+Depends: ${misc:Depends},
197 ${${python}:Depends}
198 Recommends: eatmydata, sudo, software-properties-common, gdisk
199 XB-Python-Version: ${python:Versions}
200diff --git a/packages/pkg-deps.json b/packages/pkg-deps.json
201new file mode 100644
202index 0000000..042cde7
203--- /dev/null
204+++ b/packages/pkg-deps.json
205@@ -0,0 +1 @@
206+{"debian": {"build-requires": ["debhelper", "dh-python", "dh-systemd"], "requires": ["procps"], "renames": {"argparse": {"2": "python-argparse", "3": null},"contextlib2": {"2": "python-contextlib2", "3": null}, "cheetah": {"2": "python-cheetah", "3": null}, "pyserial": {"2": "python-serial", "3": "python3-serial"}, "pyyaml": {"2": "python-yaml", "3": "python3-yaml"}, "six": {"2": "python-six", "3": "python3-six"}, "pep8": {"2": "pep8", "3": "python3-pep8"}, "pyflakes": {"2": "pyflakes", "3": "pyflakes"}}}, "redhat": {"requires": ["shadow-utils", "rsyslog", "iproute", "e2fsprogs", "net-tools", "procps", "shadow-utils", "sudo >= 1.7.2p2-3"], "build-requires": ["python-devel", "python-setuptools", "python-cheetah"], "renames": {"pyserial": {"2": "pyserial", "3": null}, "pyyaml": {"2": "PyYAML", "3": null}}}, "suse": {"build-requires": ["fdupes", "filesystem", "python-devel", "python-setuptools", "python-cheetah"], "requires": ["iproute2", "e2fsprogs", "net-tools", "procps", "sudo"], "renames": {"pyyaml": {"2": "python-yaml", "3": null}}}}
207diff --git a/packages/redhat/cloud-init.spec.in b/packages/redhat/cloud-init.spec.in
208index fd3cf93..a62d860 100644
209--- a/packages/redhat/cloud-init.spec.in
210+++ b/packages/redhat/cloud-init.spec.in
211@@ -18,21 +18,12 @@ Source0: ${archive_name}
212 BuildArch: noarch
213 BuildRoot: %{_tmppath}
214
215-BuildRequires: python-devel
216-BuildRequires: python-setuptools
217-BuildRequires: python-cheetah
218-
219-# System util packages needed
220-Requires: shadow-utils
221-Requires: rsyslog
222-Requires: iproute
223-Requires: e2fsprogs
224-Requires: net-tools
225-Requires: procps
226-Requires: shadow-utils
227-Requires: sudo >= 1.7.2p2-3
228-
229-# Install pypi 'dynamic' requirements
230+# Install 'dynamic' build reqs from *requirements.txt and pkg-deps.json
231+#for $r in $buildrequires
232+BuildRequires: ${r}
233+#end for
234+
235+# Install 'dynamic' runtime reqs from *requirements.txt and pkg-deps.json
236 #for $r in $requires
237 Requires: ${r}
238 #end for
239diff --git a/packages/suse/cloud-init.spec.in b/packages/suse/cloud-init.spec.in
240index 6ce0be8..444401c 100644
241--- a/packages/suse/cloud-init.spec.in
242+++ b/packages/suse/cloud-init.spec.in
243@@ -22,11 +22,9 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-build
244 BuildArch: noarch
245 %endif
246
247-BuildRequires: fdupes
248-BuildRequires: filesystem
249-BuildRequires: python-devel
250-BuildRequires: python-setuptools
251-BuildRequires: python-cheetah
252+#for $r in $buildrequires
253+BuildRequires: ${r}
254+#end for
255
256 %if 0%{?suse_version} && 0%{?suse_version} <= 1210
257 %define initsys sysvinit
258@@ -34,13 +32,6 @@ BuildRequires: python-cheetah
259 %define initsys systemd
260 %endif
261
262-# System util packages needed
263-Requires: iproute2
264-Requires: e2fsprogs
265-Requires: net-tools
266-Requires: procps
267-Requires: sudo
268-
269 # Install pypi 'dynamic' requirements
270 #for $r in $requires
271 Requires: ${r}
272diff --git a/tools/read-dependencies b/tools/read-dependencies
273index f434905..e01e718 100755
274--- a/tools/read-dependencies
275+++ b/tools/read-dependencies
276@@ -1,43 +1,183 @@
277 #!/usr/bin/env python
278+"""List pip dependencies or system package dependencies for cloud-init."""
279
280 # You might be tempted to rewrite this as a shell script, but you
281 # would be surprised to discover that things like 'egrep' or 'sed' may
282 # differ between Linux and *BSD.
283
284+try:
285+ from argparse import ArgumentParser
286+except ImportError:
287+ raise RuntimeError(
288+ 'Could not import python-argparse. Please install python-argparse '
289+ 'package to continue')
290+
291+import json
292 import os
293 import re
294-import sys
295 import subprocess
296+import sys
297
298-if 'CLOUD_INIT_TOP_D' in os.environ:
299- topd = os.path.realpath(os.environ.get('CLOUD_INIT_TOP_D'))
300-else:
301- topd = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
302
303-for fname in ("setup.py", "requirements.txt"):
304- if not os.path.isfile(os.path.join(topd, fname)):
305- sys.stderr.write("Unable to locate '%s' file that should "
306- "exist in cloud-init root directory." % fname)
307- sys.exit(1)
308-
309-if len(sys.argv) > 1:
310- reqfile = sys.argv[1]
311-else:
312- reqfile = "requirements.txt"
313-
314-with open(os.path.join(topd, reqfile), "r") as fp:
315- for line in fp:
316- line = line.strip()
317- if not line or line.startswith("#"):
318+# Map the appropriate package dir needed for each distro choice
319+DISTRO_PKG_TYPE_MAP = {
320+ 'centos': 'redhat',
321+ 'redhat': 'redhat',
322+ 'debian': 'debian',
323+ 'ubuntu': 'debian',
324+ 'opensuse': 'suse',
325+ 'suse': 'suse'
326+}
327+
328+DISTRO_INSTALL_PKG_CMD = {
329+ 'cent': ['yum', 'install'],
330+ 'redhat': ['yum', 'install'],
331+ 'debian': ['apt', 'install'],
332+ 'ubuntu': ['apt', 'install'],
333+ 'opensuse': ['zypper', 'install'],
334+ 'suse': ['zypper', 'install']
335+}
336+
337+PY_26_ONLY = ['argparse']
338+
339+
340+# JSON definition of distro-specific package dependencies
341+DISTRO_PKG_DEPS_PATH = "packages/pkg-deps.json"
342+
343+
344+def get_parser():
345+ """Return an argument parser for this command."""
346+ parser = ArgumentParser(description=__doc__)
347+ parser.add_argument(
348+ '-r', '--requirements-file', type=str, dest='req_file',
349+ default='requirements.txt', help='The pip-style requirements file')
350+ parser.add_argument(
351+ '-d', '--distro', type=str, choices=DISTRO_PKG_TYPE_MAP.keys(),
352+ help='The name of the distro to generate package deps for.')
353+ parser.add_argument(
354+ '-s', '--system-pkg-names', action='store_true', default=False,
355+ dest='system_pkg_names',
356+ help='The name of the distro to generate package deps for.')
357+ parser.add_argument(
358+ '-i', '--install', action='store_true', default=False,
359+ dest='install',
360+ help='When specified, install the required system packages.')
361+ parser.add_argument(
362+ '-v', '--python-version', type=str, dest='python_version', default="2",
363+ choices=["2", "3"],
364+ help='The version of python we want to generate system package '
365+ 'dependencies for.')
366+ return parser
367+
368+
369+def get_package_deps_from_json(topdir, distro):
370+ """Get a dict of build and runtime package requirements for a distro.
371+
372+ @param topdir: The root directory in which to search for the
373+ DISTRO_PKG_DEPS_PATH json blob of package requirements information.
374+ @param distro: The specific distribution shortname to pull dependencies
375+ for.
376+ @return: Dict containing "requires", "build-requires" and "rename" lists
377+ for a given distribution.
378+ """
379+ with open(os.path.join(topdir, DISTRO_PKG_DEPS_PATH), 'r') as stream:
380+ deps = json.loads(stream.read())
381+ if distro is None:
382+ return {}
383+ return deps[DISTRO_PKG_TYPE_MAP[distro]]
384+
385+
386+def parse_pip_requirements(requirements_path):
387+ """Return the pip requirement names from pip-style requirements_path."""
388+ dep_names = []
389+ with open(requirements_path, "r") as fp:
390+ for line in fp:
391+ line = line.strip()
392+ if not line or line.startswith("#"):
393+ continue
394+
395+ # remove pip-style markers
396+ dep = line.split(';')[0]
397+
398+ # remove version requirements
399+ if re.search('[>=.<]+', dep):
400+ dep_names.append(re.split("[>=.<]*", dep)[0].strip())
401+ else:
402+ dep_names.append(dep)
403+ return dep_names
404+
405+
406+def translate_pip_to_system_pkg(pip_requires, renames, python_ver="2"):
407+ """Translate pip package names to distro-specific package names.
408+
409+ @param pip_requires: List of versionless pip package names to translate.
410+ @param renames: Dict containg special case renames from pip name to system
411+ package name for the distro.
412+ """
413+ if python_ver == "2":
414+ prefix = "python-"
415+ else:
416+ prefix = "python3-"
417+ standard_pkg_name = "{0}{1}"
418+ translated_names = []
419+ for pip_name in pip_requires:
420+ pip_name = pip_name.lower()
421+ if pip_name in PY_26_ONLY and sys.version_info[0:2] > (2, 6):
422+ # Skip PY26 pkg deps unless we are actually on python 2.6
423 continue
424+ # Find a rename if present for the distro package and python version
425+ rename = renames.get(pip_name, {}).get(python_ver, None)
426+ if rename:
427+ translated_names.append(rename)
428+ else:
429+ translated_names.append(
430+ standard_pkg_name.format(prefix, pip_name))
431+ return translated_names
432+
433+
434+def main(distro):
435+ parser = get_parser()
436+ args = parser.parse_args()
437+ if 'CLOUD_INIT_TOP_D' in os.environ:
438+ topd = os.path.realpath(os.environ.get('CLOUD_INIT_TOP_D'))
439+ else:
440+ topd = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
441+ req_path = os.path.join(topd, args.req_file)
442+ if not os.path.isfile(req_path):
443+ sys.stderr.write("Unable to locate '%s' file that should "
444+ "exist in cloud-init root directory." % req_path)
445+ return 1
446+ pip_pkg_names = parse_pip_requirements(req_path)
447+ deps_from_json = get_package_deps_from_json(topd, args.distro)
448+ renames = deps_from_json.get('renames', {})
449+ translated_pip_names = translate_pip_to_system_pkg(
450+ pip_pkg_names, renames, args.python_version)
451+ all_deps = []
452+ if args.distro:
453+ all_deps.extend(
454+ translated_pip_names + deps_from_json['requires'] +
455+ deps_from_json['build-requires'])
456+ else:
457+ if args.system_pkg_names:
458+ all_deps = translated_pip_names
459+ else:
460+ all_deps = pip_pkg_names
461+ if args.install:
462+ pkg_install(all_deps, args.distro)
463+ else:
464+ print('\n'.join(all_deps))
465+
466
467- # remove pip-style markers
468- dep = line.split(';')[0]
469+def pkg_install(pkg_list, distro):
470+ """Install a list of packages using the DISTRO_INSTALL_PKG_CMD"""
471+ print("Installing deps:", ' '.join(pkg_list))
472+ cmd = ['sudo'] + DISTRO_INSTALL_PKG_CMD[distro] + pkg_list
473+ subprocess.check_call(cmd)
474
475- # remove version requirements
476- dep = re.split("[>=.<]*", dep)[0].strip()
477- print(dep)
478
479-sys.exit(0)
480+if __name__ == "__main__":
481+ parser = get_parser()
482+ args = parser.parse_args()
483+ sys.exit(main(args.distro))
484
485 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches