Patch for Bug #227013 - /boot not mounted
[moblin-image-creator.eeepc.git] / libs / SDK.py
blobf08fc1086435a3b126336775a8464b3f53cdd7f7
1 #!/usr/bin/python -tt
2 # vim: ai ts=4 sts=4 et sw=4
4 # Copyright (c) 2007 Intel Corporation
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # for more details.
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc., 59
17 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 # Embedded Linux SDK main module
21 # The SDK allows a developer to use any apt repository as seed material
22 # for building a target filesystem for an embedded device.
24 # User list available projects:
25 # -----------------------------
27 # # Input => Nothing
29 # print 'Available projects: '
30 # sdk = SDK()
31 # for key in sorted(sdk.projects.iterkeys()):
32 # project = sdk.projects[key]
33 # print '\t - %s: %s' % (project.name, project.path)
35 # User opens an existing project:
36 # -------------------------------
38 # Input => Name of existing project
40 # proj = SDK().projects[project_name]
42 # User list available platforms:
43 # ------------------------------
45 # # Input => Name of the project (a string)
47 # print 'Available platforms:'
48 # sdk = SDK()
49 # for pname in sorted(sdk.platforms.iterkeys()):
50 # print '\t - %s' % sdk.platforms[pname].name
52 # User creates a new project:
53 # ---------------------------
55 # Input => Path to the new project workspace
56 # Input => Name to give the new project
57 # Input => Description of project
58 # Input => Platform object
60 # sdk = SDK()
62 # # construct the new project
63 # proj = sdk.create_project(path, name, desc, sdk.platforms['donley'])
65 # # install the platform defined list of RPM packages into the platform
66 # # so that the platform directory can be used as a jailroot
67 # proj.install()
69 # # keep in mind, that at this point there are no target filesystems
70 # # installed in the project
72 # User list available targets installed in a project:
73 # ---------------------------------------------------
75 # Input => Project object
77 # print 'Available targets:'
78 # for key in sorted(project.targets.iterkeys()):
79 # target = project.targets[key]
80 # print '\t - %s' % (target.name)
82 # User creates a new target inside a project:
83 # -------------------------------------------
85 # Input => Project object
86 # Input => name to use for target
88 # target = project.create_target(name)
90 # User list available fsets for the platform:
91 # -------------------------------------------
93 # Input => Platform object
95 # print 'Available fsets for the %s platform:' % (platform.name)
96 # for key in platform.fset:
97 # fset = platform.fset[key]
98 # print '\t - %s' % (fset.name)
100 # User installs a fset in target:
101 # -------------------------------
103 # Input => Target object
104 # Input => fset object
106 # # you could do a normal install
107 # target.installFset(fset)
109 # # or you could install debug packages in addition to the normal packages
110 # target.installFset(fset, debug_pkgs = 1)
112 import ConfigParser
113 import gettext
114 import os
115 import re
116 import shutil
117 import socket
118 import sys
119 import tarfile
120 import tempfile
121 import time
123 import Platform
124 import Project
125 import mic_cfg
126 import pdk_utils
128 _ = gettext.lgettext
130 class SDK(object):
131 def __init__(self, progress_callback = None, status_label_callback = None,
132 path='/usr/share/pdk', var_dir = '/var/lib/moblin-image-creator/'):
133 self.var_dir = var_dir
134 self.path = os.path.realpath(os.path.abspath(os.path.expanduser(path)))
135 self.version = "- Undefined"
136 version_file = os.path.join(self.path, "version")
137 if os.path.isfile(version_file):
138 in_file = open(version_file, 'r')
139 line = in_file.readline()
140 line = line.strip()
141 result = re.search(r'\((?P<version>.*)\) (?P<distribution>.*);', line)
142 if result:
143 self.version = "- %s - %s" % (result.group('version'), result.group('distribution'))
144 in_file.close()
145 self.progress_callback_func = progress_callback
146 self.status_label_callback_func = status_label_callback
147 self.config_path = os.path.join(self.var_dir, 'projects')
148 if not os.path.isdir(self.config_path):
149 os.mkdir(self.config_path)
151 # instantiate all platforms
152 self.platforms = {}
153 dirname = os.path.join(self.path, 'platforms')
154 platform_config_file = os.path.join(dirname, "platforms.cfg")
155 if not os.path.isfile(platform_config_file):
156 raise ValueError(_("Platforms config file not found: %s") % platform_config_file)
157 config = ConfigParser.SafeConfigParser()
158 config.read(platform_config_file)
159 for section in config.sections():
160 t_dirname = os.path.join(dirname, section)
161 if not os.path.dirname(t_dirname):
162 raise ValueError(_("Platform config file: %s has a section: %s but no corresponding directory: %s") % (platform_config_file, section, t_dirname))
163 self.platforms[section] = Platform.Platform(t_dirname, section, config.items(section))
165 # discover all existing projects
166 self.discover_projects()
168 def progress_callback(self, *args):
169 if not self.progress_callback_func:
170 return
171 self.progress_callback_func(*args)
173 def status_label_callback(self, *args):
174 if not self.status_label_callback_func:
175 return
176 self.status_label_callback_func(*args)
178 def discover_projects(self):
179 self.projects = {}
180 self.obsolete_projects = set()
181 directories = [ os.path.join(self.config_path, x) for x in os.listdir(self.config_path) ]
182 # FIXME: This is here for backwards compatibility, I would think that
183 # after Jun-2008, we can delete this list
184 if os.path.exists(os.path.join(self.path, 'projects')):
185 old_directories = [ os.path.join(self.path, 'projects', x) for x in os.listdir(os.path.join(self.path, 'projects')) ]
186 directories.extend(old_directories)
187 for filename in directories:
188 full_path = os.path.join(self.config_path, filename)
189 full_path = filename
190 if not os.path.isfile(full_path):
191 continue
192 config = PackageConfig(full_path)
193 try:
194 self.projects[config.name] = Project.Project(config, self.platforms[config.platform], self.progress_callback)
195 except KeyError:
196 self.obsolete_projects.add(config.name)
197 print _("Platform %s not found. Skipping the project %s") % (config.platform, config.name)
199 def return_obsolete_projects(self):
200 return self.obsolete_projects
202 def create_project(self, parent_path, name, desc, platform, use_rootstrap = True):
204 Create a new project by specifying an install path, a name, a
205 short description, and a platform object.
207 Example:
209 # By 'creating' a project, the class will:
210 # - create a new directory (i.e. path)
211 # - create the initial directory structure
212 # - setup the project configuration files both inside the project
213 # directory, and also 'create the project config' in
214 # /usr/share/pdk/projects/
216 proj = SDK().create_project(self, '~/projects/myproject',
217 'My Project',
218 'This is a test, only a test', 'donley')
220 # after creating the project, you still need to install the platform
221 # specific packages to enable the project to be used as a jailroot
222 proj.install()
224 if not parent_path or not name or not desc or not platform:
225 raise ValueError(_("Empty argument passed in"))
226 if name in self.projects:
227 raise ValueError(_("Project: %s already exists") % name)
228 install_path = os.path.realpath(os.path.abspath(os.path.expanduser(parent_path)))
229 self.status_label_callback(_("Creating the project chroot environment"))
230 if platform.createChroot(install_path, use_rootstrap, callback = self.progress_callback) == False:
231 pdk_utils.rmtree(install_path, callback = self.progress_callback)
232 raise ValueError(_("Rootstrap Creating cancelled"))
233 # create the config file
234 self.status_label_callback(_("Creating Config file"))
235 config_path = os.path.join(self.config_path, "%s.proj" % name)
236 config_file = open(config_path, 'w')
237 config_file.write(_("NAME=%s\n") % (name))
238 config_file.write(_("PATH=%s\n") % (install_path))
239 config_file.write(_("DESC=%s\n") % (desc))
240 config_file.write(_("PLATFORM=%s\n") % (platform.name))
241 config_file.close()
242 # instantiate the project
243 self.status_label_callback(_("Initiating the project"))
244 config = PackageConfig(config_path)
245 try:
246 self.projects[name] = Project.Project(config, platform, self.progress_callback)
247 except:
248 pdk_utils.rmtree(install_path, callback = self.progress_callback)
249 os.unlink(config_path)
250 raise ValueError("%s" % (sys.exc_value))
251 self.projects[name].mount()
252 return self.projects[name]
254 def isVerboseProjectTar(self):
255 """See if we should be verbose during tarring for save/load projects"""
256 try:
257 result = mic_cfg.config.get("general", "verbose_project_tar")
258 result = int(result)
259 if result:
260 result = 1
261 else:
262 result = 0
263 except:
264 result = 0
265 return result
267 def save_project(self, project_name, filename):
268 """Save the project to the specified filename"""
269 tar_filename = filename
270 if not filename.endswith(".mic.tar.bz2"):
271 tar_filename = "%s.mic.tar.bz2" % filename
272 project = self.projects[project_name]
273 config_file = os.path.join(self.config_path, "%s.proj" % project_name)
274 # Create the compressed tarfile
275 tar_file = tarfile.open(tar_filename, "w:bz2")
276 tar_file.debug = self.isVerboseProjectTar()
277 tar_file.add(config_file, arcname = "config/save.proj")
278 print _("Creating project tarfile. This can take a long time...")
279 print _("Filename: %s") % tar_filename
280 project.tar(tar_file)
281 tar_file.close()
282 print _("Project tarfile created at: %s") % tar_filename
284 def load_project(self, project_name, project_path, filename, progressCallback = None):
285 """Load the specified filename as project_name and store it in
286 project_path"""
287 tar_filename = filename
288 if not filename.endswith(".mic.tar.bz2"):
289 raise ValueError(_("Specified project restore file: %s, does not end in .mic.tar.bz2"))
290 config_file = os.path.join(self.config_path, "%s.proj" % project_name)
291 if os.path.exists(config_file):
292 raise ValueError(_("A project already exists with that name: %s") % config_file)
293 if project_path.find(' ') != -1:
294 raise ValueError(_("Specified project path contains a space character, not allowed: %s") % project_path)
295 if os.path.exists(project_path):
296 if os.path.isdir(project_path):
297 if len(os.listdir(project_path)):
298 raise ValueError(_("Specified project-path, is a directory, but it is NOT empty: %s") % project_path)
299 else:
300 os.rmdir(project_path)
301 else:
302 raise ValueError(_("Specified project-path, exists, but it is not a directory"))
303 tempdir = tempfile.mkdtemp()
304 cwd = os.getcwd()
305 os.chdir(tempdir)
306 print _("Extracting: %s to temporary directory: %s/") % (filename, tempdir)
307 time.sleep(2)
308 if self.isVerboseProjectTar():
309 tar_options = "xfjv"
310 else:
311 tar_options = "xfj"
312 pdk_utils.execCommand("tar %s %s --numeric-owner" % (tar_options, filename), callback = progressCallback)
313 os.chdir(cwd)
314 source_config_file = os.path.join(tempdir, "config", "save.proj")
315 if not os.path.isfile(source_config_file):
316 raise ValueError(_("Project config file did not exist in project tarfile. Could not find: %s") % source_config_file)
317 source_project = os.path.join(tempdir, "project")
318 if not os.path.isdir(source_project):
319 raise ValueError(_("Project directory did not exist in project tarfile. Could not find: %s") % source_project)
320 print _("Writing new config file: %s") % config_file
321 self.copyProjectConfigFile(source_config_file, config_file, project_name, project_path)
322 print _("Moving project directory into place at: %s") % project_path
323 cmd_line = "mv -v %s %s" % (source_project, project_path)
324 print cmd_line
325 result = pdk_utils.execCommand(cmd_line)
326 if result:
327 print _("Error doing 'mv' cmd")
328 sys.exit(1)
329 print _("Removing temporary directory: %s") % tempdir
330 pdk_utils.rmtree(tempdir, callback = self.progress_callback)
331 print _("Project: %s restored to: %s") % (project_name, project_path)
333 def copyProjectConfigFile(self, source_config_file, dest_config_file, project_name, project_path):
334 """Copy the config file over and update the fields that need to be updated"""
335 config = PackageConfig(source_config_file)
336 config.set('name', project_name)
337 config.set('path', project_path)
338 config.write(dest_config_file)
340 def delete_project(self, project_name):
341 # first delete all contained targets
342 proj = self.projects[project_name]
343 for target in proj.targets:
344 self.progress_callback(None)
345 proj.delete_target(target, False, callback = self.progress_callback)
346 proj.targets.clear()
347 # and then deal with the project
348 directory_set = proj.umount()
349 if directory_set:
350 raise pdk_utils.ImageCreatorUmountError, directory_set
351 pdk_utils.rmtree(proj.path, callback = self.progress_callback)
352 os.unlink(proj.config_info.filename)
354 def getProjects(self):
355 """Return back a list containing all the projects that the SDK knows about"""
356 project_list = []
357 for key in sorted(self.projects.iterkeys()):
358 project_list.append(self.projects[key])
359 return project_list
361 def clear_rootstraps(self):
362 print _("Deleting rootstraps...")
363 for key in self.platforms.iterkeys():
364 path = self.platforms[key].path
365 for prefix in [ "build", "target" ]:
366 root_strap_path = os.path.join(path, "%s-rootstrap.tar.bz2" % prefix)
367 if os.path.exists(root_strap_path):
368 print _("Deleting: %s") % root_strap_path
369 os.unlink(root_strap_path)
370 var_dir = mic_cfg.config.get('general', 'var_dir')
371 rootstrap_dir = os.path.join(var_dir, "rootstraps")
372 if os.path.exists(rootstrap_dir):
373 print _("Deleting rootstrap directory: %s") % rootstrap_dir
374 pdk_utils.rmtree(rootstrap_dir, callback = self.progress_callback)
376 def umount(self):
377 # Unmount all of our projects
378 directory_set = set()
379 for key in sorted(self.projects.iterkeys()):
380 project = self.projects[key]
381 project.umount(directory_set = directory_set)
382 return directory_set
384 def __str__(self):
385 return ("<SDK Object: path=%s, platform=%s>" %
386 (self.path, self.platforms))
388 def __repr__(self):
389 return "SDK(path='%s')" % self.path
391 class ConfigFile(object):
393 This is a class for generically parsing configuration files that contain
394 'NAME=VALUE' pairs, each on it's own line. We probably should be using the
395 ConfigParser library instead :(
397 example usage:
399 string_vals = ['name', 'desc']
400 config = ConfigFile('/etc/myconf', string_vals);
401 print config.name
402 print config.desc
405 def __init__(self, filename, string_vals):
406 self.__filename = filename
407 self.filename = filename
408 config = open(self.__filename)
409 self.val_dict = {}
410 for line in config:
411 if re.search(r'^\s*#', line):
412 continue
413 try:
414 key, value = line.split('=')
415 except:
416 continue
417 key = key.lower().strip()
418 if key in string_vals:
419 self.set(key, value)
420 config.close()
422 def set(self, key, value):
423 key = key.lower().strip()
424 value = value.strip()
425 self.val_dict[key] = value
427 def __getattr__(self, key):
428 if key in self.val_dict:
429 return self.val_dict[key]
430 else:
431 if 'object_name' in self.__dict__:
432 object_name = self.object_name
433 else:
434 object_name = "ConfigFile"
435 raise AttributeError(_("'%s' object has no attribute '%s'") % (object_name, key))
437 def write(self, filename = None):
438 if filename == None:
439 filename = self.__filename
440 config = open(filename, 'w')
441 for key in sorted(self.val_dict.iterkeys()):
442 config.write("%s=%s\n" % (key, self.val_dict[key]))
443 # os.environ[key] = self.val_dict[key]
444 config.close()
446 def __str__(self):
447 return "ConfigFile('%s', %s)" % (self.__filename, self.val_dict)
449 class PackageConfig(ConfigFile):
450 def __init__(self, path):
451 self.object_name = 'PackageConfig'
452 ConfigFile.__init__(self, path, ['name', 'desc', 'path', 'platform'])
455 if __name__ == '__main__':
456 for path in sys.argv[1:]:
457 print SDK(path = path)