Commit | Line | Data |
---|---|---|
de9b991b JR |
1 | import os |
2 | import shutil | |
3 | import git | |
4 | import subprocess | |
5 | import logging | |
6 | ||
fe7b987e | 7 | _logger = logging.getLogger('project') |
de9b991b JR |
8 | |
9 | class Project(object): | |
10 | ||
11 | def __init__(self, label, git_path, sha1, tmpdir): | |
12 | self.label = label | |
13 | self.git_path = git_path | |
14 | self.sha1 = sha1 | |
15 | ||
16 | """ Custom configure flags in the for of ['-x', 'arg']""" | |
17 | self.custom_configure_flags = [] | |
18 | ccache = shutil.which("ccache") | |
19 | if ccache is not None: | |
20 | self.custom_configure_flags.append("CC={} gcc".format(ccache)) | |
21 | self.custom_configure_flags.append("CXX={} g++".format(ccache)) | |
22 | ||
23 | """ A collection of Project dependencies """ | |
fe7b987e JR |
24 | self.dependencies = {} |
25 | self._immutable = False | |
de9b991b JR |
26 | |
27 | # State | |
de9b991b JR |
28 | self.isBuilt = False |
29 | self.isConfigured = False | |
30 | self.isInstalled = False | |
31 | ||
fe7b987e JR |
32 | self.basedir = tmpdir |
33 | self.log_path = os.path.join(tmpdir, "log") | |
34 | self.source_path = os.path.join(tmpdir, "source") | |
35 | self.installation_path = os.path.join(tmpdir, "install") | |
36 | ||
37 | os.makedirs(self.log_path) | |
de9b991b JR |
38 | os.makedirs(self.source_path) |
39 | os.makedirs(self.installation_path) | |
de9b991b JR |
40 | |
41 | self.special_env_variables = {} | |
42 | ||
43 | # Init the repo for work | |
44 | self.checkout() | |
45 | self.bootstrap() | |
46 | ||
47 | def get_cppflags(self): | |
48 | return " -I{}/include".format(self.installation_path) | |
49 | ||
50 | def get_ldflags(self): | |
51 | return " -L{}/lib".format(self.installation_path) | |
52 | ||
53 | def get_ld_library_path(self): | |
54 | return "{}/lib".format(self.installation_path) | |
55 | ||
56 | def get_env(self): | |
57 | """Modify environment to reflect dependency""" | |
58 | cpp_flags = "" | |
59 | ld_flags = "" | |
60 | ld_library_path = "" | |
61 | ||
62 | env = os.environ.copy() | |
63 | ||
64 | for var, value in self.special_env_variables.items(): | |
65 | if var in env: | |
de9b991b | 66 | # Raise for now since no special cases is known |
fe7b987e JR |
67 | _logger.warning("% Special var % is already defined", |
68 | self.label, var) | |
de9b991b JR |
69 | raise Exception("Multiple definition of a special environment variable") |
70 | else: | |
71 | env[var] = value | |
72 | ||
fe7b987e | 73 | for key, dep in self.dependencies.items(): |
de9b991b JR |
74 | # Extra space just in case |
75 | cpp_flags += " {}".format(dep.get_cppflags()) | |
76 | ld_flags += " {}".format(dep.get_ldflags()) | |
77 | ld_library_path += "{}:".format(dep.get_ld_library_path()) | |
78 | for var, value in dep.special_env_variables.items(): | |
79 | if var in env: | |
de9b991b | 80 | # Raise for now since no special cases is known |
fe7b987e JR |
81 | _logger.warning("% Special var % is already defined", |
82 | self.label, var) | |
de9b991b JR |
83 | raise Exception("Multiple definition of a special environment variable") |
84 | else: | |
85 | env[var] = value | |
86 | ||
de9b991b JR |
87 | if cpp_flags: |
88 | if 'CPPFLAGS' in env: | |
89 | cpp_flags = env['CPPFLAGS'] + cpp_flags | |
90 | env['CPPFLAGS'] = cpp_flags | |
fe7b987e | 91 | _logger.debug("% CPPFLAGS= %s", self.label, cpp_flags) |
de9b991b JR |
92 | if ld_flags: |
93 | if 'LDFLAGS' in env: | |
94 | ld_flags = env['LDFLAGS'] + ld_flags | |
95 | env['LDFLAGS'] = ld_flags | |
fe7b987e | 96 | _logger.debug("% LDFLAGS= %s", self.label, ld_flags) |
de9b991b JR |
97 | if ld_library_path: |
98 | if 'LD_LIBRARY_PATH' in env: | |
99 | ld_library_path = env['LD_LIBRARY_PATH'] + ":" + ld_library_path | |
100 | env['LD_LIBRARY_PATH'] = ld_library_path | |
fe7b987e | 101 | _logger.debug("% LD_LIBRARY_PATH= %s", self.label, ld_library_path) |
de9b991b JR |
102 | return env |
103 | ||
104 | def autobuild(self): | |
105 | """ | |
106 | Perform the bootstrap, configuration, build and install the | |
107 | project. Build dependencies if not already built | |
108 | """ | |
fe7b987e JR |
109 | if (self.isConfigured and self.isBuilt and self.isInstalled): |
110 | return | |
111 | ||
112 | if self._immutable: | |
113 | raise Exception("Object is immutable. Illegal autobuild") | |
114 | ||
115 | for key, dep in self.dependencies.items(): | |
de9b991b JR |
116 | dep.autobuild() |
117 | ||
fe7b987e | 118 | if self.isConfigured ^ self.isBuilt ^ self.isInstalled: |
de9b991b JR |
119 | raise Exception("Project steps where manually triggered. Can't autobuild") |
120 | ||
fe7b987e | 121 | _logger.debug("% Autobuild configure", self.label) |
de9b991b | 122 | self.configure() |
fe7b987e | 123 | _logger.debug("% Autobuild build", self.label) |
de9b991b | 124 | self.build() |
fe7b987e | 125 | _logger.debug("% Autobuild install", self.label) |
de9b991b JR |
126 | self.install() |
127 | ||
128 | def checkout(self): | |
fe7b987e JR |
129 | if self._immutable: |
130 | raise Exception("Object is immutable. Illegal checkout") | |
131 | ||
de9b991b JR |
132 | repo = git.Repo.clone_from(self.git_path, self.source_path) |
133 | commit = repo.commit(self.sha1) | |
134 | repo.head.reference = commit | |
135 | assert repo.head.is_detached | |
136 | repo.head.reset(index=True, working_tree=True) | |
137 | ||
138 | def bootstrap(self): | |
139 | """ | |
140 | Bootstap the project. Raise subprocess.CalledProcessError on | |
141 | bootstrap error. | |
142 | """ | |
fe7b987e JR |
143 | if self._immutable: |
144 | raise Exception("Object is immutable. Illegal bootstrap") | |
145 | ||
146 | out = os.path.join(self.log_path, "bootstrap.out") | |
147 | err = os.path.join(self.log_path, "bootstrap.err") | |
148 | ||
de9b991b | 149 | os.chdir(self.source_path) |
fe7b987e JR |
150 | with open(out, 'w') as stdout, open(err, 'w') as stderr: |
151 | p = subprocess.run(['./bootstrap'], stdout=stdout, stderr=stderr) | |
de9b991b JR |
152 | p.check_returncode() |
153 | return p | |
154 | ||
155 | def configure(self): | |
156 | """ | |
157 | Configure the project. | |
158 | Raises subprocess.CalledProcessError on configure error | |
159 | """ | |
fe7b987e JR |
160 | if self._immutable: |
161 | raise Exception("Object is immutable. Illegal configure") | |
162 | ||
de9b991b | 163 | # Check that all our dependencies were actually installed |
fe7b987e | 164 | for key, dep in self.dependencies.items(): |
de9b991b JR |
165 | if not dep.isInstalled: |
166 | # TODO: Custom exception here Dependency Error | |
167 | raise Exception("Dependency project flagged as not installed") | |
168 | ||
fe7b987e JR |
169 | out = os.path.join(self.log_path, "configure.out") |
170 | err = os.path.join(self.log_path, "configure.err") | |
171 | ||
de9b991b JR |
172 | os.chdir(self.source_path) |
173 | args = ['./configure'] | |
174 | prefix = '--prefix={}'.format(self.installation_path) | |
175 | args.append(prefix) | |
176 | args.extend(self.custom_configure_flags) | |
177 | ||
178 | # TODO: log output and add INFO log point | |
fe7b987e JR |
179 | with open(out, 'w') as stdout, open(err, 'w') as stderr: |
180 | p = subprocess.run(args, env=self.get_env(), stdout=stdout, | |
181 | stderr=stderr) | |
de9b991b JR |
182 | p.check_returncode() |
183 | self.isConfigured = True | |
184 | return p | |
185 | ||
186 | def build(self): | |
187 | """ | |
188 | Build the project. Raise subprocess.CalledProcessError on build | |
189 | error. | |
190 | """ | |
fe7b987e JR |
191 | if self._immutable: |
192 | raise Exception("Object is immutable. Illegal build") | |
193 | ||
194 | out = os.path.join(self.log_path, "build.out") | |
195 | err = os.path.join(self.log_path, "build.err") | |
196 | ||
de9b991b JR |
197 | os.chdir(self.source_path) |
198 | args = ['make'] | |
199 | env = self.get_env() | |
200 | env['CFLAGS'] = '-g -O0' | |
201 | ||
202 | # Number of usable cpu | |
203 | # https://docs.python.org/3/library/os.html#os.cpu_count | |
204 | num_cpu = str(len(os.sched_getaffinity(0))) | |
205 | args.append('-j') | |
206 | args.append(num_cpu) | |
207 | ||
208 | # TODO: log output and add INFO log point with args | |
fe7b987e JR |
209 | with open(out, 'w') as stdout, open(err, 'w') as stderr: |
210 | p = subprocess.run(args, env=env, stdout=stdout, | |
211 | stderr=stderr) | |
de9b991b JR |
212 | p.check_returncode() |
213 | self.isBuilt = True | |
214 | return p | |
215 | ||
216 | def install(self): | |
217 | """ | |
218 | Install the project. Raise subprocess.CalledProcessError on | |
219 | bootstrap error | |
220 | """ | |
fe7b987e JR |
221 | if self._immutable: |
222 | raise Exception("Object is immutable. Illegal install") | |
223 | ||
224 | out = os.path.join(self.log_path, "build.out") | |
225 | err = os.path.join(self.log_path, "build.err") | |
226 | ||
de9b991b JR |
227 | os.chdir(self.source_path) |
228 | args = ['make', 'install'] | |
229 | ||
230 | # TODO: log output and add INFO log point | |
fe7b987e JR |
231 | with open(out, 'w') as stdout, open(err, 'w') as stderr: |
232 | p = subprocess.run(args, env=self.get_env(), stdout=stdout, | |
233 | stderr=stderr) | |
de9b991b JR |
234 | p.check_returncode() |
235 | self.isInstalled = True | |
236 | return p | |
237 | ||
238 | def cleanup(self): | |
239 | if os.path.exists(self.source_path): | |
240 | shutil.rmtree(self.source_path) | |
241 | if os.path.exists(self.installation_path): | |
242 | shutil.rmtree(self.installation_path) | |
243 | ||
244 | ||
245 | class Lttng_modules(Project): | |
246 | def bootstrap(self): | |
247 | pass | |
248 | ||
249 | def configure(self): | |
250 | pass | |
251 | ||
252 | def install(self): | |
fe7b987e JR |
253 | if self._immutable: |
254 | raise Exception("Object is immutable. Illegal install") | |
de9b991b JR |
255 | os.chdir(self.source_path) |
256 | args = ['make', 'INSTALL_MOD_PATH={}'.format(self.installation_path), | |
257 | 'modules_install'] | |
258 | p = subprocess.run(args, env=self.get_env(), stdout=subprocess.PIPE, | |
259 | stderr=subprocess.PIPE) | |
260 | p.check_returncode() | |
261 | ||
262 | # Perform a local depmod | |
263 | args = ['depmod', '-b', self.installation_path] | |
264 | p = subprocess.run(args, env=self.get_env()) | |
265 | p.check_returncode() | |
266 | self.isInstalled = True | |
267 | ||
268 | ||
269 | class Lttng_ust(Project): | |
270 | def __init__(self, label, git_path, sha1, tmpdir): | |
271 | super(Lttng_ust, self).__init__(label=label, git_path=git_path, | |
272 | sha1=sha1, tmpdir=tmpdir) | |
273 | self.custom_configure_flags.extend(['--disable-man-pages']) | |
274 | ||
275 | ||
276 | class Lttng_tools(Project): | |
277 | pass | |
278 | ||
279 | ||
280 | class Babeltrace(Project): | |
281 | pass |