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
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 # -----------------------------
29 # print 'Available projects: '
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:'
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
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
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)
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()
141 result
= re
.search(r
'\((?P<version>.*)\) (?P<distribution>.*);', line
)
143 self
.version
= "- %s - %s" % (result
.group('version'), result
.group('distribution'))
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
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
:
171 self
.progress_callback_func(*args
)
173 def status_label_callback(self
, *args
):
174 if not self
.status_label_callback_func
:
176 self
.status_label_callback_func(*args
)
178 def discover_projects(self
):
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
)
190 if not os
.path
.isfile(full_path
):
192 config
= PackageConfig(full_path
)
194 self
.projects
[config
.name
] = Project
.Project(config
, self
.platforms
[config
.platform
], self
.progress_callback
)
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.
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',
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
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
))
242 # instantiate the project
243 self
.status_label_callback(_("Initiating the project"))
244 config
= PackageConfig(config_path
)
246 self
.projects
[name
] = Project
.Project(config
, platform
, self
.progress_callback
)
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"""
257 result
= mic_cfg
.config
.get("general", "verbose_project_tar")
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
)
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
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
)
300 os
.rmdir(project_path
)
302 raise ValueError(_("Specified project-path, exists, but it is not a directory"))
303 tempdir
= tempfile
.mkdtemp()
306 print _("Extracting: %s to temporary directory: %s/") % (filename
, tempdir
)
308 if self
.isVerboseProjectTar():
312 pdk_utils
.execCommand("tar %s %s --numeric-owner" % (tar_options
, filename
), callback
= progressCallback
)
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
)
325 result
= pdk_utils
.execCommand(cmd_line
)
327 print _("Error doing 'mv' cmd")
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
)
347 # and then deal with the project
348 directory_set
= proj
.umount()
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"""
357 for key
in sorted(self
.projects
.iterkeys()):
358 project_list
.append(self
.projects
[key
])
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
)
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
)
385 return ("<SDK Object: path=%s, platform=%s>" %
386 (self
.path
, self
.platforms
))
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 :(
399 string_vals = ['name', 'desc']
400 config = ConfigFile('/etc/myconf', string_vals);
405 def __init__(self
, filename
, string_vals
):
406 self
.__filename
= filename
407 self
.filename
= filename
408 config
= open(self
.__filename
)
411 if re
.search(r
'^\s*#', line
):
414 key
, value
= line
.split('=')
417 key
= key
.lower().strip()
418 if key
in string_vals
:
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
]
431 if 'object_name' in self
.__dict
__:
432 object_name
= self
.object_name
434 object_name
= "ConfigFile"
435 raise AttributeError(_("'%s' object has no attribute '%s'") % (object_name
, key
))
437 def write(self
, 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]
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
)