2 ###############################################################################
4 # Pakfire - The IPFire package management system #
5 # Copyright (C) 2011 Pakfire development team #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
38 log
= logging
.getLogger("pakfire")
40 import pakfire
.lzma
as lzma
41 import pakfire
.util
as util
43 from pakfire
.constants
import *
44 from pakfire
.i18n
import _
46 from file import BinaryPackage
, InnerTarFile
, InnerTarFileXz
, SourcePackage
48 class Packager(object):
49 payload_compression
= None
51 def __init__(self
, pakfire
, pkg
):
52 self
.pakfire
= pakfire
59 for file in self
.tmpfiles
:
60 if not os
.path
.exists(file):
63 log
.debug("Removing tmpfile: %s" % file)
65 if os
.path
.isdir(file):
70 def mktemp(self
, directory
=False):
72 filename
= os
.path
.join("/", LOCAL_TMP_PATH
, util
.random_string())
75 f
= tempfile
.NamedTemporaryFile(mode
="w", delete
=False)
80 self
.tmpfiles
.append(filename
)
84 def save(self
, filename
):
85 # Create a new tar archive.
86 tar
= tarfile
.TarFile(filename
, mode
="w", format
=tarfile
.PAX_FORMAT
)
88 # Add package formation information.
89 # Must always be the first file in the archive.
90 formatfile
= self
.create_package_format()
91 tar
.add(formatfile
, arcname
="pakfire-format")
93 # XXX make sure all files belong to the root user
95 # Create checksum file.
96 chksumsfile
= self
.mktemp()
97 chksums
= open(chksumsfile
, "w")
99 # Add all files to tar file.
100 for arcname
, filename
in self
.files
:
101 tar
.add(filename
, arcname
=arcname
)
103 # Calculating the hash sum of the added file
104 # and store it in the chksums file.
108 buf
= f
.read(BUFFER_SIZE
)
115 chksums
.write("%-10s %s\n" % (arcname
, h
.hexdigest()))
117 # Close checksum file and attach it to the end.
119 tar
.add(chksumsfile
, "chksums")
121 # Close the tar file.
124 def add(self
, filename
, arcname
=None):
126 arcname
= os
.path
.basename(filename
)
128 log
.debug("Adding %s (as %s) to tarball." % (filename
, arcname
))
129 self
.files
.append((arcname
, filename
))
131 def create_package_format(self
):
132 filename
= self
.mktemp()
134 f
= open(filename
, "w")
135 f
.write("%s\n" % PACKAGE_FORMAT
)
140 def create_filelist(self
, datafile
):
141 filelist
= self
.mktemp()
143 f
= open(filelist
, "w")
145 if self
.payload_compression
== "xz":
146 datafile
= InnerTarFileXz
.open(datafile
)
148 datafile
= InnerTarFile
.open(datafile
)
155 log
.debug(" %s %-8s %-8s %s %6s %s" % \
156 (tarfile
.filemode(m
.mode
), m
.uname
, m
.gname
,
157 "%d-%02d-%02d %02d:%02d:%02d" % time
.localtime(m
.mtime
)[:6],
158 util
.format_size(m
.size
), m
.name
))
160 f
.write("%(name)-40s %(type)1s %(size)-10d %(uname)-10s %(gname)-10s %(mode)-6d %(mtime)-12d" \
161 % m
.get_info(tarfile
.ENCODING
, "strict"))
163 # Calculate SHA512 hash of regular files.
165 mobj
= datafile
.extractfile(m
)
169 buf
= mobj
.read(BUFFER_SIZE
)
175 f
.write(" %s" % h
.hexdigest())
179 caps
= m
.pax_headers
.get("PAKFIRE.capabilities", None)
181 f
.write(" %s" % caps
)
195 raise NotImplementedError
197 def getsize(self
, filename
):
198 if tarfile
.is_tarfile(filename
):
199 return os
.path
.getsize(filename
)
202 f
= lzma
.LZMAFile(filename
)
205 buf
= f
.read(BUFFER_SIZE
)
215 class BinaryPackager(Packager
):
216 payload_compression
= "xz"
218 def __init__(self
, pakfire
, pkg
, builder
, buildroot
):
219 Packager
.__init
__(self
, pakfire
, pkg
)
221 self
.builder
= builder
222 self
.buildroot
= buildroot
224 def create_metafile(self
, datafile
):
225 info
= collections
.defaultdict(lambda: "")
227 # Extract datafile in temporary directory and scan for dependencies.
228 tmpdir
= self
.mktemp(directory
=True)
230 if self
.payload_compression
== "xz":
231 tarfile
= InnerTarFileXz
.open(datafile
)
233 tarfile
= InnerTarFile
.open(datafile
)
235 tarfile
.extractall(path
=tmpdir
)
238 # Run the dependency tracker.
239 self
.pkg
.track_dependencies(self
.builder
, tmpdir
)
241 # Generic package information including Pakfire information.
243 "pakfire_version" : PAKFIRE_VERSION
,
244 "uuid" : self
.pkg
.uuid
,
248 # Include distribution information.
249 info
.update(self
.pakfire
.distro
.info
)
250 info
.update(self
.pkg
.info
)
252 # Update package information for string formatting.
254 "groups" : " ".join(self
.pkg
.groups
),
255 "prerequires" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
256 for d
in self
.pkg
.prerequires
]),
257 "requires" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
258 for d
in self
.pkg
.requires
]),
259 "provides" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
260 for d
in self
.pkg
.provides
]),
261 "conflicts" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
262 for d
in self
.pkg
.conflicts
]),
263 "obsoletes" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
264 for d
in self
.pkg
.obsoletes
]),
267 # Format description.
268 description
= [PACKAGE_INFO_DESCRIPTION_LINE
% l \
269 for l
in util
.text_wrap(self
.pkg
.description
, length
=80)]
270 info
["description"] = "\n".join(description
)
274 # Package it built right now.
275 "build_time" : int(time
.time()),
276 "build_id" : uuid
.uuid4(),
279 # Installed size (equals size of the uncompressed tarball).
281 "inst_size" : self
.getsize(datafile
),
284 metafile
= self
.mktemp()
286 f
= open(metafile
, "w")
287 f
.write(PACKAGE_INFO
% info
)
292 def create_datafile(self
):
296 # List of all patterns, which grows.
297 patterns
= self
.pkg
.files
299 for pattern
in patterns
:
300 # Check if we are running in include or exclude mode.
301 if pattern
.startswith("!"):
304 # Strip the ! character.
305 pattern
= pattern
[1:]
309 # Expand file to point to chroot.
310 if pattern
.startswith("/"):
311 pattern
= pattern
[1:]
312 pattern
= os
.path
.join(self
.buildroot
, pattern
)
314 # Recognize the type of the pattern. Patterns could be a glob
315 # pattern that is expanded here or just a directory which will
316 # be included recursively.
317 if "*" in pattern
or "?" in pattern
or ("[" in pattern
and "]" in pattern
):
318 _patterns
= glob
.glob(pattern
)
320 _patterns
= [pattern
,]
322 for pattern
in _patterns
:
323 # Try to stat the pattern. If that is not successful, we cannot go on.
329 # Add directories recursively but skip those symlinks
330 # that point to a directory.
331 if os
.path
.isdir(pattern
) and not os
.path
.islink(pattern
):
332 # Add directory itself.
333 files
.append(pattern
)
335 for dir, subdirs
, _files
in os
.walk(pattern
):
336 for subdir
in subdirs
:
337 if subdir
in ORPHAN_DIRECTORIES
:
340 subdir
= os
.path
.join(dir, subdir
)
344 file = os
.path
.join(dir, file)
347 # All other files are just added.
349 files
.append(pattern
)
352 orphan_directories
= [os
.path
.join(self
.buildroot
, d
) for d
in ORPHAN_DIRECTORIES
]
355 for file in includes
:
356 # Skip if file is already in the file set or
357 # marked to be excluded from this archive.
358 if file in excludes
or file in files
:
361 # Skip orphan directories.
362 if file in orphan_directories
and not os
.listdir(file):
363 log
.debug("Found an orphaned directory: %s" % file)
369 file = os
.path
.dirname(file)
371 if file == self
.buildroot
:
374 if not file in files
:
380 message
= "%-10s : %s" % (_("Packaging"), self
.pkg
.friendly_name
)
381 pb
= util
.make_progress(message
, len(files
), eta
=False)
383 datafile
= self
.mktemp()
384 if self
.payload_compression
== "xz":
385 tar
= InnerTarFileXz
.open(datafile
, mode
="w")
387 tar
= InnerTarFile
.open(datafile
, mode
="w")
389 # All files in the tarball are relative to this directory.
390 basedir
= self
.buildroot
399 if os
.path
.normpath(file) == os
.path
.normpath(basedir
):
402 # Name of the file in the archive.
403 arcname
= "/%s" % os
.path
.relpath(file, basedir
)
405 # Add file to tarball.
406 tar
.add(file, arcname
=arcname
, recursive
=False)
408 # Remove all packaged files.
409 for file in reversed(files
):
410 # It's okay if we cannot remove directories,
411 # when they are not empty.
412 if os
.path
.isdir(file):
424 file = os
.path
.dirname(file)
426 if not file.startswith(basedir
):
437 # Finish progressbar.
443 def create_scriptlets(self
):
446 # Collect all prerequires for the scriptlets.
449 for scriptlet_name
in SCRIPTS
:
450 scriptlet
= self
.pkg
.get_scriptlet(scriptlet_name
)
455 # Write script to a file.
456 scriptlet_file
= self
.mktemp()
458 if scriptlet
["lang"] == "bin":
463 raise Exception, "Cannot open script file: %s" % lang
["path"]
465 s
= open(scriptlet_file
, "wb")
468 buf
= f
.read(BUFFER_SIZE
)
477 elif scriptlet
["lang"] == "shell":
478 s
= open(scriptlet_file
, "w")
480 # Write shell script to file.
481 s
.write("#!/bin/sh -e\n\n")
482 s
.write(scriptlet
["scriptlet"])
483 s
.write("\n\nexit 0\n")
487 if scriptlet_name
in SCRIPTS_PREREQUIRES
:
488 # Shell scripts require a shell to be executed.
489 prerequires
.append("/bin/sh")
491 prerequires
+= self
.builder
.find_prerequires(scriptlet_file
)
494 raise Exception, "Unknown scriptlet language: %s" % scriptlet
["lang"]
496 scriptlets
.append((scriptlet_name
, scriptlet_file
))
498 # Cleanup prerequires.
499 self
.pkg
.update_prerequires(prerequires
)
503 def create_configs(self
, datafile
):
504 if self
.payload_compression
== "xz":
505 datafile
= InnerTarFileXz
.open(datafile
)
507 datafile
= InnerTarFile
.open(datafile
)
509 members
= datafile
.getmembers()
514 # Find all directories in the config file list.
515 for file in self
.pkg
.configfiles
:
516 if file.startswith("/"):
519 for member
in members
:
520 if member
.name
== file and member
.isdir():
521 configdirs
.append(file)
523 for configdir
in configdirs
:
524 for member
in members
:
525 if not member
.isdir() and member
.name
.startswith(configdir
):
526 configfiles
.append(member
.name
)
528 for pattern
in self
.pkg
.configfiles
:
529 if pattern
.startswith("/"):
530 pattern
= pattern
[1:]
532 for member
in members
:
533 if not fnmatch
.fnmatch(member
.name
, pattern
):
536 if member
.name
in configfiles
:
539 configfiles
.append(member
.name
)
541 # Sort list alphabetically.
544 configsfile
= self
.mktemp()
546 f
= open(configsfile
, "w")
547 for file in configfiles
:
548 f
.write("%s\n" % file)
553 def run(self
, resultdir
):
554 # Add all files to this package.
555 datafile
= self
.create_datafile()
557 # Get filelist from datafile.
558 filelist
= self
.create_filelist(datafile
)
559 configs
= self
.create_configs(datafile
)
561 # Create script files.
562 scriptlets
= self
.create_scriptlets()
564 metafile
= self
.create_metafile(datafile
)
566 # Add files to the tar archive in correct order.
567 self
.add(metafile
, "info")
568 self
.add(filelist
, "filelist")
569 self
.add(configs
, "configs")
570 self
.add(datafile
, "data.img")
572 for scriptlet_name
, scriptlet_file
in scriptlets
:
573 self
.add(scriptlet_file
, "scriptlets/%s" % scriptlet_name
)
575 # Build the final package.
576 tempfile
= self
.mktemp()
579 # Add architecture information to path.
580 resultdir
= "%s/%s" % (resultdir
, self
.pkg
.arch
)
582 if not os
.path
.exists(resultdir
):
583 os
.makedirs(resultdir
)
585 resultfile
= os
.path
.join(resultdir
, self
.pkg
.package_filename
)
586 log
.info("Saving package to %s" % resultfile
)
588 os
.link(tempfile
, resultfile
)
590 shutil
.copy2(tempfile
, resultfile
)
592 return BinaryPackage(self
.pakfire
, self
.pakfire
.repos
.dummy
, resultfile
)
595 class SourcePackager(Packager
):
596 payload_compression
= None
598 def create_metafile(self
, datafile
):
599 info
= collections
.defaultdict(lambda: "")
601 # Generic package information including Pakfire information.
603 "pakfire_version" : PAKFIRE_VERSION
,
607 # Include distribution information.
608 info
.update(self
.pakfire
.distro
.info
)
609 info
.update(self
.pkg
.info
)
611 # Size is the size of the (uncompressed) datafile.
612 info
["inst_size"] = self
.getsize(datafile
)
614 # Update package information for string formatting.
615 requires
= [PACKAGE_INFO_DEPENDENCY_LINE
% r
for r
in self
.pkg
.requires
]
617 "groups" : " ".join(self
.pkg
.groups
),
618 "requires" : "\n".join(requires
),
621 # Format description.
622 description
= [PACKAGE_INFO_DESCRIPTION_LINE
% l \
623 for l
in util
.text_wrap(self
.pkg
.description
, length
=80)]
624 info
["description"] = "\n".join(description
)
628 # Package it built right now.
629 "build_time" : int(time
.time()),
630 "build_id" : uuid
.uuid4(),
633 # Arches equals supported arches.
634 info
["arch"] = self
.pkg
.supported_arches
637 # XXX replace this by the payload hash
639 "uuid" : uuid
.uuid4(),
642 metafile
= self
.mktemp()
644 f
= open(metafile
, "w")
645 f
.write(PACKAGE_INFO
% info
)
650 def create_datafile(self
):
651 # Create a list of all files that have to be put into the
655 # Download all files that go into the package.
656 for file in self
.pkg
.download():
657 assert os
.path
.getsize(file), "Don't package empty files"
658 files
.append(("files/%s" % os
.path
.basename(file), file))
660 # Add all files in the package directory.
661 for file in self
.pkg
.files
:
662 files
.append((os
.path
.relpath(file, self
.pkg
.path
), file))
664 # Add files in alphabetical order.
668 message
= "%-10s : %s" % (_("Packaging"), self
.pkg
.friendly_name
)
669 pb
= util
.make_progress(message
, len(files
), eta
=False)
671 filename
= self
.mktemp()
672 if self
.payload_compression
== "xz":
673 datafile
= InnerTarFileXz
.open(filename
, mode
="w")
675 datafile
= InnerTarFile
.open(filename
, mode
="w")
678 for arcname
, file in files
:
683 datafile
.add(file, arcname
)
691 def run(self
, resultdir
):
692 # Create resultdir if it does not exist yet.
693 if not os
.path
.exists(resultdir
):
694 os
.makedirs(resultdir
)
696 log
.info(_("Building source package %s:") % self
.pkg
.package_filename
)
698 # The filename where this source package is saved at.
699 target_filename
= os
.path
.join(resultdir
, self
.pkg
.package_filename
)
701 # Add datafile to package.
702 datafile
= self
.create_datafile()
704 # Create filelist out of data.
705 filelist
= self
.create_filelist(datafile
)
708 metafile
= self
.create_metafile(datafile
)
710 # Add files to the tar archive in correct order.
711 self
.add(metafile
, "info")
712 self
.add(filelist
, "filelist")
713 self
.add(datafile
, "data.img")
715 # Build the final tarball.
717 self
.save(target_filename
)
719 # Remove the target file when anything went wrong.
720 os
.unlink(target_filename
)
723 return target_filename