Accept predefined CPPFLAGS, CFLAGS, LDFLAGS
[deliverable/lttng-ivc.git] / lttng_ivc / utils / project.py
CommitLineData
efdd48db
JR
1# Copyright (c) 2017 Jonathan Rajotte-Julien <jonathan.rajotte-julien@efficios.com>
2#
3# Permission is hereby granted, free of charge, to any person obtaining a copy
4# of this software and associated documentation files (the "Software"), to deal
5# in the Software without restriction, including without limitation the rights
6# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7# copies of the Software, and to permit persons to whom the Software is
8# furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice shall be included in all
11# copies or substantial portions of the Software.
12#
13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19# SOFTWARE.
20
de9b991b
JR
21import os
22import shutil
23import git
24import subprocess
25import logging
cb87ff89 26import lttng_ivc.settings as Settings
5908717c 27import pprint
cb87ff89
JR
28
29from lttng_ivc.utils.utils import sha256_checksum
816f8cc1 30from lttng_ivc.utils.utils import find_dir, find_file
de9b991b 31
fe7b987e 32_logger = logging.getLogger('project')
de9b991b
JR
33
34class Project(object):
35
36 def __init__(self, label, git_path, sha1, tmpdir):
37 self.label = label
38 self.git_path = git_path
39 self.sha1 = sha1
40
41 """ Custom configure flags in the for of ['-x', 'arg']"""
42 self.custom_configure_flags = []
43 ccache = shutil.which("ccache")
44 if ccache is not None:
45 self.custom_configure_flags.append("CC={} gcc".format(ccache))
46 self.custom_configure_flags.append("CXX={} g++".format(ccache))
97d494b0 47 self.custom_configure_flags.append("CFLAGS=-g -O0".format(ccache))
de9b991b
JR
48
49 """ A collection of Project dependencies """
fe7b987e 50 self.dependencies = {}
cb87ff89 51 # used for project cache and pickle validation
fe7b987e 52 self._immutable = False
cb87ff89 53 self._py_file_checksum = sha256_checksum(Settings.project_py_file_location)
de9b991b
JR
54
55 # State
de9b991b
JR
56 self.isBuilt = False
57 self.isConfigured = False
58 self.isInstalled = False
ab63b97e 59 self.skip = False
de9b991b 60
fe7b987e
JR
61 self.basedir = tmpdir
62 self.log_path = os.path.join(tmpdir, "log")
63 self.source_path = os.path.join(tmpdir, "source")
64 self.installation_path = os.path.join(tmpdir, "install")
65
97d494b0
JR
66 if os.path.isdir(self.basedir):
67 # Perform cleanup since it should not happen
68 shutil.rmtree(self.basedir)
69
fe7b987e 70 os.makedirs(self.log_path)
de9b991b
JR
71 os.makedirs(self.source_path)
72 os.makedirs(self.installation_path)
de9b991b
JR
73
74 self.special_env_variables = {}
75
76 # Init the repo for work
77 self.checkout()
78 self.bootstrap()
79
18aedaf9
JR
80 def add_special_env_variable(self, key, value):
81 if key in self.special_env_variables:
82 _logger.warning("{} Special var {} is already defined".format(
83 self.label, key))
84 raise Exception("Multiple definition of a special environment variable")
85 self.special_env_variables[key] = value
86
de9b991b 87 def get_cppflags(self):
18aedaf9
JR
88 cppflags = ["-I{}/include".format(self.installation_path)]
89 for key, dep in self.dependencies.items():
90 cppflags.append(dep.get_cppflags())
91
92 return " ".join(cppflags)
de9b991b
JR
93
94 def get_ldflags(self):
18aedaf9
JR
95 ldflags = ["-L{}/lib".format(self.installation_path)]
96 for key, dep in self.dependencies.items():
97 ldflags.append(dep.get_ldflags())
98 return " ".join(ldflags)
de9b991b
JR
99
100 def get_ld_library_path(self):
18aedaf9
JR
101 library_path = ["{}/lib".format(self.installation_path)]
102 for key, dep in self.dependencies.items():
103 library_path.append(dep.get_ld_library_path())
104 return ":".join(library_path)
105
106 def get_bin_path(self):
107 bin_path = ["{}/bin".format(self.installation_path)]
108 for key, dep in self.dependencies.items():
109 bin_path.append(dep.get_bin_path())
110 return ":".join(bin_path)
de9b991b
JR
111
112 def get_env(self):
113 """Modify environment to reflect dependency"""
18aedaf9
JR
114 env_var = {"CPPFLAGS": (self.get_cppflags(), " "),
115 "LDFLAGS": (self.get_ldflags(), " "),
116 "LD_LIBRARY_PATH": (self.get_ld_library_path(), ":"),
117 }
de9b991b
JR
118
119 env = os.environ.copy()
120
121 for var, value in self.special_env_variables.items():
122 if var in env:
ca2f2d59
JR
123 if var == "LD_LIBRARY_PATH":
124 env[var] = ":".join([env[var], value])
0cd453ab
JR
125 elif var == "CPPFLAGS" or var == "CFLAGS" or var == "LDFLAGS":
126 env[var] = " ".join([env[var], value])
ca2f2d59
JR
127 else:
128 _logger.warning("{} Special var {} is already defined".format(
129 self.label, var))
130 raise Exception("Multiple definition of a special environment variable")
de9b991b
JR
131 else:
132 env[var] = value
133
fe7b987e 134 for key, dep in self.dependencies.items():
de9b991b 135 # Extra space just in case
de9b991b
JR
136 for var, value in dep.special_env_variables.items():
137 if var in env:
de9b991b 138 # Raise for now since no special cases is known
18aedaf9
JR
139 _logger.warning("{} Special var {} is already defined".format(
140 self.label, var))
de9b991b
JR
141 raise Exception("Multiple definition of a special environment variable")
142 else:
143 env[var] = value
144
18aedaf9
JR
145 for var, (value, delimiter) in env_var.items():
146 tmp = [value]
147 if var in env:
148 tmp.append(env[var])
149 env[var] = delimiter.join(tmp)
150
de9b991b
JR
151 return env
152
153 def autobuild(self):
154 """
155 Perform the bootstrap, configuration, build and install the
156 project. Build dependencies if not already built
157 """
fe7b987e
JR
158 if (self.isConfigured and self.isBuilt and self.isInstalled):
159 return
160
161 if self._immutable:
162 raise Exception("Object is immutable. Illegal autobuild")
163
164 for key, dep in self.dependencies.items():
de9b991b
JR
165 dep.autobuild()
166
fe7b987e 167 if self.isConfigured ^ self.isBuilt ^ self.isInstalled:
de9b991b
JR
168 raise Exception("Project steps where manually triggered. Can't autobuild")
169
97d494b0
JR
170 _logger.debug("{} Autobuild configure".format(self.label))
171 try:
172 self.configure()
173 except subprocess.CalledProcessError as e:
174 _logger.error("{} Configure failed. See {} for more details.".format(self.label, self.log_path))
175 raise e
176
177 _logger.debug("{} Autobuild build".format(self.label))
178 try:
179 self.build()
180 except subprocess.CalledProcessError as e:
181 _logger.error("{} Build failed. See {} for more details.".format(self.label, self.log_path))
182 raise e
183
184 _logger.debug("{} Autobuild install".format(self.label))
185 try:
186 self.install()
187 except subprocess.CalledProcessError as e:
188 _logger.error("{} Install failed. See {} for more details.".format(self.label, self.log_path))
189 raise e
de9b991b
JR
190
191 def checkout(self):
fe7b987e
JR
192 if self._immutable:
193 raise Exception("Object is immutable. Illegal checkout")
194
de9b991b
JR
195 repo = git.Repo.clone_from(self.git_path, self.source_path)
196 commit = repo.commit(self.sha1)
197 repo.head.reference = commit
198 assert repo.head.is_detached
199 repo.head.reset(index=True, working_tree=True)
200
201 def bootstrap(self):
202 """
203 Bootstap the project. Raise subprocess.CalledProcessError on
204 bootstrap error.
205 """
fe7b987e
JR
206 if self._immutable:
207 raise Exception("Object is immutable. Illegal bootstrap")
208
209 out = os.path.join(self.log_path, "bootstrap.out")
210 err = os.path.join(self.log_path, "bootstrap.err")
211
de9b991b 212 os.chdir(self.source_path)
fe7b987e
JR
213 with open(out, 'w') as stdout, open(err, 'w') as stderr:
214 p = subprocess.run(['./bootstrap'], stdout=stdout, stderr=stderr)
de9b991b
JR
215 p.check_returncode()
216 return p
217
218 def configure(self):
219 """
220 Configure the project.
221 Raises subprocess.CalledProcessError on configure error
222 """
fe7b987e
JR
223 if self._immutable:
224 raise Exception("Object is immutable. Illegal configure")
225
de9b991b 226 # Check that all our dependencies were actually installed
fe7b987e 227 for key, dep in self.dependencies.items():
de9b991b
JR
228 if not dep.isInstalled:
229 # TODO: Custom exception here Dependency Error
230 raise Exception("Dependency project flagged as not installed")
231
fe7b987e
JR
232 out = os.path.join(self.log_path, "configure.out")
233 err = os.path.join(self.log_path, "configure.err")
5908717c
JR
234 env_file = os.path.join(self.log_path, "configure.env")
235
236 env = self.get_env()
237
238 with open(env_file, 'w') as tmp:
239 pprint.pprint(env, stream=tmp)
fe7b987e 240
de9b991b
JR
241 os.chdir(self.source_path)
242 args = ['./configure']
243 prefix = '--prefix={}'.format(self.installation_path)
244 args.append(prefix)
245 args.extend(self.custom_configure_flags)
246
247 # TODO: log output and add INFO log point
fe7b987e 248 with open(out, 'w') as stdout, open(err, 'w') as stderr:
5908717c 249 p = subprocess.run(args, env=env, stdout=stdout,
fe7b987e 250 stderr=stderr)
de9b991b
JR
251 p.check_returncode()
252 self.isConfigured = True
253 return p
254
255 def build(self):
256 """
257 Build the project. Raise subprocess.CalledProcessError on build
258 error.
259 """
fe7b987e
JR
260 if self._immutable:
261 raise Exception("Object is immutable. Illegal build")
262
263 out = os.path.join(self.log_path, "build.out")
264 err = os.path.join(self.log_path, "build.err")
265
de9b991b
JR
266 os.chdir(self.source_path)
267 args = ['make']
268 env = self.get_env()
de9b991b
JR
269
270 # Number of usable cpu
271 # https://docs.python.org/3/library/os.html#os.cpu_count
272 num_cpu = str(len(os.sched_getaffinity(0)))
273 args.append('-j')
274 args.append(num_cpu)
97d494b0 275 args.append('V=1')
de9b991b
JR
276
277 # TODO: log output and add INFO log point with args
fe7b987e
JR
278 with open(out, 'w') as stdout, open(err, 'w') as stderr:
279 p = subprocess.run(args, env=env, stdout=stdout,
280 stderr=stderr)
de9b991b
JR
281 p.check_returncode()
282 self.isBuilt = True
283 return p
284
285 def install(self):
286 """
287 Install the project. Raise subprocess.CalledProcessError on
288 bootstrap error
289 """
fe7b987e
JR
290 if self._immutable:
291 raise Exception("Object is immutable. Illegal install")
292
97d494b0
JR
293 out = os.path.join(self.log_path, "install.out")
294 err = os.path.join(self.log_path, "install.err")
fe7b987e 295
de9b991b
JR
296 os.chdir(self.source_path)
297 args = ['make', 'install']
298
299 # TODO: log output and add INFO log point
fe7b987e
JR
300 with open(out, 'w') as stdout, open(err, 'w') as stderr:
301 p = subprocess.run(args, env=self.get_env(), stdout=stdout,
302 stderr=stderr)
de9b991b
JR
303 p.check_returncode()
304 self.isInstalled = True
305 return p
306
307 def cleanup(self):
308 if os.path.exists(self.source_path):
309 shutil.rmtree(self.source_path)
310 if os.path.exists(self.installation_path):
311 shutil.rmtree(self.installation_path)
312
313
314class Lttng_modules(Project):
97d494b0
JR
315 def __init__(self, label, git_path, sha1, tmpdir):
316 super(Lttng_modules, self).__init__(label=label, git_path=git_path,
317 sha1=sha1, tmpdir=tmpdir)
f0acf3f3 318 self.add_special_env_variable("MODPROBE_OPTIONS","-d {}".format(self.installation_path))
97d494b0 319
de9b991b
JR
320 def bootstrap(self):
321 pass
322
323 def configure(self):
324 pass
325
326 def install(self):
fe7b987e
JR
327 if self._immutable:
328 raise Exception("Object is immutable. Illegal install")
de9b991b
JR
329 os.chdir(self.source_path)
330 args = ['make', 'INSTALL_MOD_PATH={}'.format(self.installation_path),
331 'modules_install']
332 p = subprocess.run(args, env=self.get_env(), stdout=subprocess.PIPE,
333 stderr=subprocess.PIPE)
334 p.check_returncode()
335
336 # Perform a local depmod
337 args = ['depmod', '-b', self.installation_path]
338 p = subprocess.run(args, env=self.get_env())
339 p.check_returncode()
340 self.isInstalled = True
341
2dd375bf
JR
342 def autobuild(self):
343 try:
344 super(Lttng_modules, self).autobuild()
345 except subprocess.CalledProcessError as e:
346 self.skip = True
347
de9b991b
JR
348
349class Lttng_ust(Project):
350 def __init__(self, label, git_path, sha1, tmpdir):
351 super(Lttng_ust, self).__init__(label=label, git_path=git_path,
352 sha1=sha1, tmpdir=tmpdir)
353 self.custom_configure_flags.extend(['--disable-man-pages'])
e5cbfcca
JR
354 self.custom_configure_flags.extend(['--enable-python-agent'])
355 self.custom_configure_flags.extend(['--enable-java-agent-jul'])
356
74eb7096
JR
357 jul_path = os.path.join(self.installation_path,
358 "share/java/liblttng-ust-agent.jar")
8e2c5f79 359 classpath = ":".join([jul_path, '.'])
74eb7096 360 self.add_special_env_variable("CLASSPATH", classpath)
de9b991b 361
2094d672
JR
362 def install(self):
363 super(Lttng_ust, self).install()
364 python_path = find_dir(self.installation_path, "lttngust")
365 if python_path:
366 # Fetch the parent of lttngust folder
367 python_path = os.path.dirname(python_path)
368 self.add_special_env_variable("PYTHONPATH", python_path)
369
370
de9b991b
JR
371
372class Lttng_tools(Project):
18aedaf9
JR
373 def __init__(self, label, git_path, sha1, tmpdir):
374 super(Lttng_tools, self).__init__(label=label, git_path=git_path,
375 sha1=sha1, tmpdir=tmpdir)
376 self.add_special_env_variable("LTTNG_SESSION_CONFIG_XSD_PATH",
377 os.path.join(self.installation_path, "share/xml/lttng/"))
de9b991b 378
816f8cc1
JR
379 # Find the mi xsd
380 for xsd in Settings.mi_xsd_file_name:
381 mi = find_file(self.source_path, xsd)
382 if mi:
383 break
384 if not mi:
385 raise Exception("MI xsd not found")
386 self.mi_xsd = mi
387
de9b991b
JR
388
389class Babeltrace(Project):
390 pass
This page took 0.040335 seconds and 5 git commands to generate.