]> git.proxmox.com Git - ceph.git/blame - ceph/src/cephadm/build.py
update ceph source to reef 18.2.1
[ceph.git] / ceph / src / cephadm / build.py
CommitLineData
1e59de90
TL
1#!/usr/bin/python3
2"""Build cephadm from one or more files into a standalone executable.
3"""
4# TODO: If cephadm is being built and packaged within a format such as RPM
5# do we have to do anything special wrt passing in the version
6# of python to build with? Even with the intermediate cmake layer?
7
8import argparse
9import compileall
10import logging
11import os
12import pathlib
13import shutil
14import subprocess
15import tempfile
16import sys
17
18HAS_ZIPAPP = False
19try:
20 import zipapp
21
22 HAS_ZIPAPP = True
23except ImportError:
24 pass
25
26
27log = logging.getLogger(__name__)
28
29
aee94f69
TL
30_VALID_VERS_VARS = [
31 "CEPH_GIT_VER",
32 "CEPH_GIT_NICE_VER",
33 "CEPH_RELEASE",
34 "CEPH_RELEASE_NAME",
35 "CEPH_RELEASE_TYPE",
36]
37
38
1e59de90
TL
39def _reexec(python):
40 """Switch to the selected version of python by exec'ing into the desired
41 python path.
42 Sets the _BUILD_PYTHON_SET env variable as a sentinel to indicate exec has
43 been performed.
44 """
45 env = os.environ.copy()
46 env["_BUILD_PYTHON_SET"] = python
47 os.execvpe(python, [python, __file__] + sys.argv[1:], env)
48
49
50def _did_rexec():
51 """Returns true if the process has already exec'ed into the desired python
52 version.
53 """
54 return bool(os.environ.get("_BUILD_PYTHON_SET", ""))
55
56
aee94f69 57def _build(dest, src, versioning_vars=None):
1e59de90
TL
58 """Build the binary."""
59 os.chdir(src)
60 tempdir = pathlib.Path(tempfile.mkdtemp(suffix=".cephadm.build"))
61 log.debug("working in %s", tempdir)
62 try:
63 if os.path.isfile("requirements.txt"):
64 _install_deps(tempdir)
65 log.info("Copying contents")
66 # TODO: currently the only file relevant to a compiled cephadm is the
67 # cephadm.py file. Once cephadm is broken up into multiple py files
68 # (and possibly other libs from python-common, etc) we'll want some
69 # sort organized structure to track what gets copied into the
70 # dir to be zipped. For now we just have a simple call to copy
71 # (and rename) the one file we care about.
72 shutil.copy("cephadm.py", tempdir / "__main__.py")
aee94f69
TL
73 if versioning_vars:
74 generate_version_file(versioning_vars, tempdir / "_version.py")
1e59de90
TL
75 _compile(dest, tempdir)
76 finally:
77 shutil.rmtree(tempdir)
78
79
80def _compile(dest, tempdir):
81 """Compile the zipapp."""
82 log.info("Byte-compiling py to pyc")
83 compileall.compile_dir(
84 tempdir,
85 maxlevels=16,
86 legacy=True,
87 quiet=1,
88 workers=0,
89 )
90 # TODO we could explicitly pass a python version here
91 log.info("Constructing the zipapp file")
92 try:
93 zipapp.create_archive(
94 source=tempdir,
95 target=dest,
96 interpreter=sys.executable,
97 compressed=True,
98 )
99 log.info("Zipapp created with compression")
100 except TypeError:
101 # automatically fall back to uncompressed
102 zipapp.create_archive(
103 source=tempdir,
104 target=dest,
105 interpreter=sys.executable,
106 )
107 log.info("Zipapp created without compression")
108
109
110def _install_deps(tempdir):
111 """Install dependencies with pip."""
112 # TODO we could explicitly pass a python version here
113 log.info("Installing dependencies")
114 # apparently pip doesn't have an API, just a cli.
115 subprocess.check_call(
116 [
117 sys.executable,
118 "-m",
119 "pip",
120 "install",
121 "--requirement",
122 "requirements.txt",
123 "--target",
124 tempdir,
125 ]
126 )
127
128
aee94f69
TL
129def generate_version_file(versioning_vars, dest):
130 log.info("Generating version file")
131 log.debug("versioning_vars=%r", versioning_vars)
132 with open(dest, "w") as fh:
133 print("# GENERATED FILE -- do not edit", file=fh)
134 for key, value in versioning_vars:
135 print(f"{key} = {value!r}", file=fh)
136
137
138def version_kv_pair(value):
139 if "=" not in value:
140 raise argparse.ArgumentTypeError(f"not a key=value pair: {value!r}")
141 key, value = value.split("=", 1)
142 if key not in _VALID_VERS_VARS:
143 raise argparse.ArgumentTypeError(f"Unexpected key: {key!r}")
144 return key, value
145
146
1e59de90
TL
147def main():
148 handler = logging.StreamHandler(sys.stdout)
149 handler.setFormatter(logging.Formatter("cephadm/build.py: %(message)s"))
150 log.addHandler(handler)
151 log.setLevel(logging.INFO)
152
153 log.debug("argv: %r", sys.argv)
154 parser = argparse.ArgumentParser()
155 parser.add_argument(
156 "dest", help="Destination path name for new cephadm binary"
157 )
158 parser.add_argument(
159 "--source", help="Directory containing cephadm sources"
160 )
161 parser.add_argument(
162 "--python", help="The path to the desired version of python"
163 )
aee94f69
TL
164 parser.add_argument(
165 "--set-version-var",
166 "-S",
167 type=version_kv_pair,
168 dest="version_vars",
169 action="append",
170 help="Set a key=value pair in the generated version info file",
171 )
1e59de90
TL
172 args = parser.parse_args()
173
174 if not _did_rexec() and args.python:
175 _reexec(args.python)
176
177 log.info(
178 "Python Version: {v.major}.{v.minor}.{v.micro}".format(
179 v=sys.version_info
180 )
181 )
182 log.info("Args: %s", vars(args))
183 if not HAS_ZIPAPP:
184 # Unconditionally display an error that the version of python
185 # lacks zipapp (probably too old).
186 print("error: zipapp module not found", file=sys.stderr)
187 print(
188 "(zipapp is available in Python 3.5 or later."
189 " are you using a new enough version?)",
190 file=sys.stderr,
191 )
192 sys.exit(2)
193 if args.source:
194 source = pathlib.Path(args.source).absolute()
195 else:
196 source = pathlib.Path(__file__).absolute().parent
197 dest = pathlib.Path(args.dest).absolute()
198 log.info("Source Dir: %s", source)
199 log.info("Destination Path: %s", dest)
aee94f69 200 _build(dest, source, versioning_vars=args.version_vars)
1e59de90
TL
201
202
203if __name__ == "__main__":
204 main()