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