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 ###############################################################################
40 import pakfire
.compress
41 import pakfire
.util
as util
43 from pakfire
.constants
import *
44 from pakfire
.i18n
import _
46 from file import BinaryPackage
, InnerTarFile
, SourcePackage
48 class Packager(object):
49 def __init__(self
, pakfire
, pkg
):
50 self
.pakfire
= pakfire
57 for file in self
.tmpfiles
:
58 if not os
.path
.exists(file):
61 logging
.debug("Removing tmpfile: %s" % file)
63 if os
.path
.isdir(file):
68 def mktemp(self
, directory
=False):
69 # XXX use real mk(s)temp here
70 filename
= os
.path
.join("/", LOCAL_TMP_PATH
, util
.random_string())
75 self
.tmpfiles
.append(filename
)
79 def save(self
, filename
):
80 # Create a new tar archive.
81 tar
= tarfile
.TarFile(filename
, mode
="w", format
=tarfile
.PAX_FORMAT
)
83 # Add package formation information.
84 # Must always be the first file in the archive.
85 formatfile
= self
.create_package_format()
86 tar
.add(formatfile
, arcname
="pakfire-format")
88 # XXX make sure all files belong to the root user
90 # Create checksum file.
91 chksumsfile
= self
.mktemp()
92 chksums
= open(chksumsfile
, "w")
94 # Add all files to tar file.
95 for arcname
, filename
in self
.files
:
96 tar
.add(filename
, arcname
=arcname
)
98 # Calculating the hash sum of the added file
99 # and store it in the chksums file.
103 buf
= f
.read(BUFFER_SIZE
)
110 chksums
.write("%-10s %s\n" % (arcname
, h
.hexdigest()))
112 # Close checksum file and attach it to the end.
114 tar
.add(chksumsfile
, "chksums")
116 # Close the tar file.
119 def add(self
, filename
, arcname
=None):
121 arcname
= os
.path
.basename(filename
)
123 logging
.debug("Adding %s (as %s) to tarball." % (filename
, arcname
))
124 self
.files
.append((arcname
, filename
))
126 def create_package_format(self
):
127 filename
= self
.mktemp()
129 f
= open(filename
, "w")
130 f
.write("%s\n" % PACKAGE_FORMAT
)
135 def create_filelist(self
, datafile
):
136 filelist
= self
.mktemp()
138 f
= open(filelist
, "w")
139 datafile
= InnerTarFile(datafile
)
141 for m
in datafile
.getmembers():
142 logging
.debug(" %s %-8s %-8s %s %6s %s" % \
143 (tarfile
.filemode(m
.mode
), m
.uname
, m
.gname
,
144 "%d-%02d-%02d %02d:%02d:%02d" % time
.localtime(m
.mtime
)[:6],
145 util
.format_size(m
.size
), m
.name
))
147 f
.write("%(name)-40s %(type)1s %(size)-10d %(uname)-10s %(gname)-10s %(mode)-6d %(mtime)-12d" \
148 % m
.get_info(tarfile
.ENCODING
, "strict"))
150 # Calculate SHA512 hash of regular files.
152 mobj
= datafile
.extractfile(m
)
156 buf
= mobj
.read(BUFFER_SIZE
)
162 f
.write(" %s\n" % h
.hexdigest())
164 # For other files, just finish the line.
176 raise NotImplementedError
179 class BinaryPackager(Packager
):
180 def __init__(self
, pakfire
, pkg
, builder
, buildroot
):
181 Packager
.__init
__(self
, pakfire
, pkg
)
183 self
.builder
= builder
184 self
.buildroot
= buildroot
186 def create_metafile(self
, datafile
):
187 info
= collections
.defaultdict(lambda: "")
189 # Extract datafile in temporary directory and scan for dependencies.
190 tmpdir
= self
.mktemp(directory
=True)
192 tarfile
= InnerTarFile(datafile
)
193 tarfile
.extractall(path
=tmpdir
)
196 # Run the dependency tracker.
197 self
.pkg
.track_dependencies(self
.builder
, tmpdir
)
199 # Generic package information including Pakfire information.
201 "pakfire_version" : PAKFIRE_VERSION
,
202 "uuid" : uuid
.uuid4(),
205 # Include distribution information.
206 info
.update(self
.pakfire
.distro
.info
)
207 info
.update(self
.pkg
.info
)
209 # Update package information for string formatting.
211 "groups" : " ".join(self
.pkg
.groups
),
212 "prerequires" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
213 for d
in self
.pkg
.prerequires
]),
214 "requires" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
215 for d
in self
.pkg
.requires
]),
216 "provides" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
217 for d
in self
.pkg
.provides
]),
218 "conflicts" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
219 for d
in self
.pkg
.conflicts
]),
220 "obsoletes" : "\n".join([PACKAGE_INFO_DEPENDENCY_LINE
% d \
221 for d
in self
.pkg
.obsoletes
]),
224 # Format description.
225 description
= [PACKAGE_INFO_DESCRIPTION_LINE
% l \
226 for l
in util
.text_wrap(self
.pkg
.description
, length
=80)]
227 info
["description"] = "\n".join(description
)
231 # Package it built right now.
232 "build_time" : int(time
.time()),
233 "build_id" : uuid
.uuid4(),
236 # Installed size (equals size of the uncompressed tarball).
238 "inst_size" : os
.path
.getsize(datafile
),
241 metafile
= self
.mktemp()
243 f
= open(metafile
, "w")
244 f
.write(PACKAGE_INFO
% info
)
249 def create_datafile(self
):
253 # List of all patterns, which grows.
254 patterns
= self
.pkg
.files
256 for pattern
in patterns
:
257 # Check if we are running in include or exclude mode.
258 if pattern
.startswith("!"):
261 # Strip the ! character.
262 pattern
= pattern
[1:]
266 # Expand file to point to chroot.
267 if pattern
.startswith("/"):
268 pattern
= pattern
[1:]
269 pattern
= os
.path
.join(self
.buildroot
, pattern
)
271 # Recognize the type of the pattern. Patterns could be a glob
272 # pattern that is expanded here or just a directory which will
273 # be included recursively.
274 if "*" in pattern
or "?" in pattern
or ("[" in pattern
and "]" in pattern
):
275 _patterns
= glob
.glob(pattern
)
277 _patterns
= [pattern
,]
279 for pattern
in _patterns
:
280 if not os
.path
.exists(pattern
):
283 # Add directories recursively...
284 if os
.path
.isdir(pattern
):
285 # Add directory itself.
286 files
.append(pattern
)
288 for dir, subdirs
, _files
in os
.walk(pattern
):
289 for subdir
in subdirs
:
290 if subdir
in ORPHAN_DIRECTORIES
:
293 subdir
= os
.path
.join(dir, subdir
)
297 file = os
.path
.join(dir, file)
300 # all other files are just added.
302 files
.append(pattern
)
305 for file in includes
:
306 # Skip if file is already in the file set or
307 # marked to be excluded from this archive.
308 if file in excludes
or file in files
:
315 message
= "%-10s : %s" % (_("Packaging"), self
.pkg
.friendly_name
)
316 pb
= util
.make_progress(message
, len(files
), eta
=False)
318 datafile
= self
.mktemp()
319 tar
= InnerTarFile(datafile
, mode
="w")
321 # All files in the tarball are relative to this directory.
322 basedir
= self
.buildroot
331 if os
.path
.normpath(file) == os
.path
.normpath(basedir
):
334 arcname
= "/%s" % os
.path
.relpath(file, basedir
)
336 # Special handling for directories.
337 if os
.path
.isdir(file):
338 # Empty directories that are in the list of ORPHAN_DIRECTORIES
339 # can be skipped and removed.
340 if arcname
in ORPHAN_DIRECTORIES
and not os
.listdir(file):
341 logging
.debug("Found an orphaned directory: %s" % arcname
)
349 # Add file to tarball.
350 tar
.add(file, arcname
=arcname
, recursive
=False)
352 # Remove all packaged files.
353 for file in reversed(files
):
354 if not os
.path
.exists(file):
357 # It's okay if we cannot remove directories,
358 # when they are not empty.
359 if os
.path
.isdir(file):
368 file = os
.path
.dirname(file)
370 if not file.startswith(basedir
):
381 # Finish progressbar.
387 def create_scriptlets(self
):
390 for scriptlet_name
in SCRIPTS
:
391 scriptlet
= self
.pkg
.get_scriptlet(scriptlet_name
)
396 # Write script to a file.
397 scriptlet_file
= self
.mktemp()
399 if scriptlet
["lang"] == "bin":
404 raise Exception, "Cannot open script file: %s" % lang
["path"]
406 s
= open(scriptlet_file
, "wb")
409 buf
= f
.read(BUFFER_SIZE
)
418 elif scriptlet
["lang"] == "shell":
419 s
= open(scriptlet_file
, "w")
421 # Write shell script to file.
422 s
.write("#!/bin/sh -e\n\n")
423 s
.write(scriptlet
["scriptlet"])
424 s
.write("\n\nexit 0\n")
429 raise Exception, "Unknown scriptlet language: %s" % scriptlet
["lang"]
431 scriptlets
.append((scriptlet_name
, scriptlet_file
))
433 # XXX scan for script dependencies
437 def create_configs(self
, datafile
):
438 datafile
= InnerTarFile(datafile
)
440 members
= datafile
.getmembers()
445 # Find all directories in the config file list.
446 for file in self
.pkg
.configfiles
:
447 if file.startswith("/"):
450 for member
in members
:
451 if member
.name
== file and member
.isdir():
452 configdirs
.append(file)
454 for configdir
in configdirs
:
455 for member
in members
:
456 if not member
.isdir() and member
.name
.startswith(configdir
):
457 configfiles
.append(member
.name
)
459 for pattern
in self
.pkg
.configfiles
:
460 if pattern
.startswith("/"):
461 pattern
= pattern
[1:]
463 for member
in members
:
464 if not fnmatch
.fnmatch(member
.name
, pattern
):
467 if member
.name
in configfiles
:
470 configfiles
.append(member
.name
)
472 # Sort list alphabetically.
475 configsfile
= self
.mktemp()
477 f
= open(configsfile
, "w")
478 for file in configfiles
:
479 f
.write("%s\n" % file)
484 def compress_datafile(self
, datafile
, algo
="xz"):
485 outputfile
= self
.mktemp()
487 # Compress the datafile with the choosen algorithm.
488 pakfire
.compress
.compress_file(datafile
, outputfile
, algo
=algo
,
489 progress
=True, message
=_("Compressing %s") % self
.pkg
.friendly_name
)
491 # We do not need the uncompressed output anymore.
494 # The outputfile becomes out new datafile.
497 def run(self
, resultdir
):
498 # Add all files to this package.
499 datafile
= self
.create_datafile()
501 # Get filelist from datafile.
502 filelist
= self
.create_filelist(datafile
)
503 configs
= self
.create_configs(datafile
)
505 # Create script files.
506 scriptlets
= self
.create_scriptlets()
508 metafile
= self
.create_metafile(datafile
)
510 # XXX make xz in variable
511 datafile
= self
.compress_datafile(datafile
, algo
="xz")
513 # Add files to the tar archive in correct order.
514 self
.add(metafile
, "info")
515 self
.add(filelist
, "filelist")
516 self
.add(configs
, "configs")
517 self
.add(datafile
, "data.img")
519 for scriptlet_name
, scriptlet_file
in scriptlets
:
520 self
.add(scriptlet_file
, "scriptlets/%s" % scriptlet_name
)
522 # Build the final package.
523 tempfile
= self
.mktemp()
526 # Add architecture information to path.
527 resultdir
= "%s/%s" % (resultdir
, self
.pkg
.arch
)
529 if not os
.path
.exists(resultdir
):
530 os
.makedirs(resultdir
)
532 resultfile
= os
.path
.join(resultdir
, self
.pkg
.package_filename
)
533 logging
.info("Saving package to %s" % resultfile
)
535 os
.link(tempfile
, resultfile
)
537 shutil
.copy2(tempfile
, resultfile
)
539 return BinaryPackage(self
.pakfire
, self
.pakfire
.repos
.dummy
, resultfile
)
542 class SourcePackager(Packager
):
543 def create_metafile(self
, datafile
):
544 info
= collections
.defaultdict(lambda: "")
546 # Generic package information including Pakfire information.
548 "pakfire_version" : PAKFIRE_VERSION
,
551 # Include distribution information.
552 info
.update(self
.pakfire
.distro
.info
)
553 info
.update(self
.pkg
.info
)
555 # Update package information for string formatting.
556 requires
= [PACKAGE_INFO_DEPENDENCY_LINE
% r
for r
in self
.pkg
.requires
]
558 "groups" : " ".join(self
.pkg
.groups
),
559 "requires" : "\n".join(requires
),
562 # Format description.
563 description
= [PACKAGE_INFO_DESCRIPTION_LINE
% l \
564 for l
in util
.text_wrap(self
.pkg
.description
, length
=80)]
565 info
["description"] = "\n".join(description
)
569 # Package it built right now.
570 "build_time" : int(time
.time()),
571 "build_id" : uuid
.uuid4(),
575 # XXX replace this by the payload hash
577 "uuid" : uuid
.uuid4(),
580 metafile
= self
.mktemp()
582 f
= open(metafile
, "w")
583 f
.write(PACKAGE_INFO
% info
)
588 def create_datafile(self
):
589 filename
= self
.mktemp()
590 datafile
= InnerTarFile(filename
, mode
="w")
592 # Add all downloaded files to the package.
593 for file in self
.pkg
.download():
594 datafile
.add(file, "files/%s" % os
.path
.basename(file))
596 # Add all files in the package directory.
597 for file in sorted(self
.pkg
.files
):
598 arcname
= os
.path
.relpath(file, self
.pkg
.path
)
599 datafile
.add(file, arcname
)
605 def run(self
, resultdirs
=[]):
608 logging
.info(_("Building source package %s:") % self
.pkg
.package_filename
)
610 # Add datafile to package.
611 datafile
= self
.create_datafile()
613 # Create filelist out of data.
614 filelist
= self
.create_filelist(datafile
)
617 metafile
= self
.create_metafile(datafile
)
619 # Add files to the tar archive in correct order.
620 self
.add(metafile
, "info")
621 self
.add(filelist
, "filelist")
622 self
.add(datafile
, "data.img")
624 # Build the final tarball.
625 tempfile
= self
.mktemp()
628 for resultdir
in resultdirs
:
629 # XXX sometimes, there has been a None in resultdirs
633 resultdir
= "%s/%s" % (resultdir
, self
.pkg
.arch
)
635 if not os
.path
.exists(resultdir
):
636 os
.makedirs(resultdir
)
638 resultfile
= os
.path
.join(resultdir
, self
.pkg
.package_filename
)
639 logging
.info("Saving package to %s" % resultfile
)
641 os
.link(tempfile
, resultfile
)
643 shutil
.copy2(tempfile
, resultfile
)
645 # Dump package information.
646 pkg
= SourcePackage(self
.pakfire
, self
.pakfire
.repos
.dummy
, tempfile
)
647 for line
in pkg
.dump(long=True).splitlines():