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 ###############################################################################
39 import pakfire
.compress
40 import pakfire
.util
as util
42 from pakfire
.constants
import *
43 from pakfire
.i18n
import _
45 from file import BinaryPackage
, InnerTarFile
, SourcePackage
47 class Packager(object):
48 def __init__(self
, pakfire
, pkg
):
49 self
.pakfire
= pakfire
56 for file in self
.tmpfiles
:
57 if not os
.path
.exists(file):
60 logging
.debug("Removing tmpfile: %s" % file)
62 if os
.path
.isdir(file):
67 def mktemp(self
, directory
=False):
68 # XXX use real mk(s)temp here
69 filename
= os
.path
.join("/", LOCAL_TMP_PATH
, util
.random_string())
74 self
.tmpfiles
.append(filename
)
78 def save(self
, filename
):
79 # Create a new tar archive.
80 tar
= tarfile
.TarFile(filename
, mode
="w", format
=tarfile
.PAX_FORMAT
)
82 # Add package formation information.
83 # Must always be the first file in the archive.
84 formatfile
= self
.create_package_format()
85 tar
.add(formatfile
, arcname
="pakfire-format")
87 # XXX make sure all files belong to the root user
89 # Create checksum file.
90 chksumsfile
= self
.mktemp()
91 chksums
= open(chksumsfile
, "w")
93 # Add all files to tar file.
94 for arcname
, filename
in self
.files
:
95 tar
.add(filename
, arcname
=arcname
)
97 # Calculating the hash sum of the added file
98 # and store it in the chksums file.
102 buf
= f
.read(BUFFER_SIZE
)
109 chksums
.write("%-10s %s\n" % (arcname
, h
.hexdigest()))
111 # Close checksum file and attach it to the end.
113 tar
.add(chksumsfile
, "chksums")
115 # Close the tar file.
118 def add(self
, filename
, arcname
=None):
120 arcname
= os
.path
.basename(filename
)
122 logging
.debug("Adding %s (as %s) to tarball." % (filename
, arcname
))
123 self
.files
.append((arcname
, filename
))
125 def create_package_format(self
):
126 filename
= self
.mktemp()
128 f
= open(filename
, "w")
129 f
.write("%s\n" % PACKAGE_FORMAT
)
134 def create_filelist(self
, datafile
):
135 filelist
= self
.mktemp()
137 f
= open(filelist
, "w")
138 datafile
= InnerTarFile(datafile
)
140 for m
in datafile
.getmembers():
141 logging
.debug(" %s %-8s %-8s %s %6s %s" % \
142 (tarfile
.filemode(m
.mode
), m
.uname
, m
.gname
,
143 "%d-%02d-%02d %02d:%02d:%02d" % time
.localtime(m
.mtime
)[:6],
144 util
.format_size(m
.size
), m
.name
))
146 f
.write("%(name)-40s %(type)1s %(size)-10d %(uname)-10s %(gname)-10s %(mode)-6d %(mtime)-12d" \
147 % m
.get_info(tarfile
.ENCODING
, "strict"))
149 # Calculate SHA512 hash of regular files.
151 mobj
= datafile
.extractfile(m
)
155 buf
= mobj
.read(BUFFER_SIZE
)
161 f
.write(" %s\n" % h
.hexdigest())
163 # For other files, just finish the line.
175 raise NotImplementedError
178 class BinaryPackager(Packager
):
179 def __init__(self
, pakfire
, pkg
, builder
, buildroot
):
180 Packager
.__init
__(self
, pakfire
, pkg
)
182 self
.builder
= builder
183 self
.buildroot
= buildroot
185 def create_metafile(self
, datafile
):
186 info
= collections
.defaultdict(lambda: "")
188 # Extract datafile in temporary directory and scan for dependencies.
189 tmpdir
= self
.mktemp(directory
=True)
191 tarfile
= InnerTarFile(datafile
)
192 tarfile
.extractall(path
=tmpdir
)
195 # Run the dependency tracker.
196 self
.pkg
.track_dependencies(self
.builder
, tmpdir
)
198 # Generic package information including Pakfire information.
200 "pakfire_version" : PAKFIRE_VERSION
,
201 "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 orphan_directories
= [os
.path
.join(self
.buildroot
, d
) for d
in ORPHAN_DIRECTORIES
]
308 for file in includes
:
309 # Skip if file is already in the file set or
310 # marked to be excluded from this archive.
311 if file in excludes
or file in files
:
314 # Skip orphan directories.
315 if file in orphan_directories
and not os
.listdir(file):
316 logging
.debug("Found an orphaned directory: %s" % file)
322 file = os
.path
.dirname(file)
324 if file == self
.buildroot
:
327 if not file in files
:
333 message
= "%-10s : %s" % (_("Packaging"), self
.pkg
.friendly_name
)
334 pb
= util
.make_progress(message
, len(files
), eta
=False)
336 datafile
= self
.mktemp()
337 tar
= InnerTarFile(datafile
, mode
="w")
339 # All files in the tarball are relative to this directory.
340 basedir
= self
.buildroot
349 if os
.path
.normpath(file) == os
.path
.normpath(basedir
):
352 # Name of the file in the archive.
353 arcname
= "/%s" % os
.path
.relpath(file, basedir
)
355 # Add file to tarball.
356 tar
.add(file, arcname
=arcname
, recursive
=False)
358 # Remove all packaged files.
359 for file in reversed(files
):
360 # It's okay if we cannot remove directories,
361 # when they are not empty.
362 if os
.path
.isdir(file):
374 file = os
.path
.dirname(file)
376 if not file.startswith(basedir
):
387 # Finish progressbar.
393 def create_scriptlets(self
):
396 for scriptlet_name
in SCRIPTS
:
397 scriptlet
= self
.pkg
.get_scriptlet(scriptlet_name
)
402 # Write script to a file.
403 scriptlet_file
= self
.mktemp()
405 if scriptlet
["lang"] == "bin":
410 raise Exception, "Cannot open script file: %s" % lang
["path"]
412 s
= open(scriptlet_file
, "wb")
415 buf
= f
.read(BUFFER_SIZE
)
424 elif scriptlet
["lang"] == "shell":
425 s
= open(scriptlet_file
, "w")
427 # Write shell script to file.
428 s
.write("#!/bin/sh -e\n\n")
429 s
.write(scriptlet
["scriptlet"])
430 s
.write("\n\nexit 0\n")
435 raise Exception, "Unknown scriptlet language: %s" % scriptlet
["lang"]
437 scriptlets
.append((scriptlet_name
, scriptlet_file
))
439 # XXX scan for script dependencies
443 def create_configs(self
, datafile
):
444 datafile
= InnerTarFile(datafile
)
446 members
= datafile
.getmembers()
451 # Find all directories in the config file list.
452 for file in self
.pkg
.configfiles
:
453 if file.startswith("/"):
456 for member
in members
:
457 if member
.name
== file and member
.isdir():
458 configdirs
.append(file)
460 for configdir
in configdirs
:
461 for member
in members
:
462 if not member
.isdir() and member
.name
.startswith(configdir
):
463 configfiles
.append(member
.name
)
465 for pattern
in self
.pkg
.configfiles
:
466 if pattern
.startswith("/"):
467 pattern
= pattern
[1:]
469 for member
in members
:
470 if not fnmatch
.fnmatch(member
.name
, pattern
):
473 if member
.name
in configfiles
:
476 configfiles
.append(member
.name
)
478 # Sort list alphabetically.
481 configsfile
= self
.mktemp()
483 f
= open(configsfile
, "w")
484 for file in configfiles
:
485 f
.write("%s\n" % file)
490 def compress_datafile(self
, datafile
, algo
="xz"):
491 outputfile
= self
.mktemp()
493 # Compress the datafile with the choosen algorithm.
494 pakfire
.compress
.compress_file(datafile
, outputfile
, algo
=algo
,
495 progress
=True, message
=_("Compressing %s") % self
.pkg
.friendly_name
)
497 # We do not need the uncompressed output anymore.
500 # The outputfile becomes out new datafile.
503 def run(self
, resultdir
):
504 # Add all files to this package.
505 datafile
= self
.create_datafile()
507 # Get filelist from datafile.
508 filelist
= self
.create_filelist(datafile
)
509 configs
= self
.create_configs(datafile
)
511 # Create script files.
512 scriptlets
= self
.create_scriptlets()
514 metafile
= self
.create_metafile(datafile
)
516 # XXX make xz in variable
517 datafile
= self
.compress_datafile(datafile
, algo
="xz")
519 # Add files to the tar archive in correct order.
520 self
.add(metafile
, "info")
521 self
.add(filelist
, "filelist")
522 self
.add(configs
, "configs")
523 self
.add(datafile
, "data.img")
525 for scriptlet_name
, scriptlet_file
in scriptlets
:
526 self
.add(scriptlet_file
, "scriptlets/%s" % scriptlet_name
)
528 # Build the final package.
529 tempfile
= self
.mktemp()
532 # Add architecture information to path.
533 resultdir
= "%s/%s" % (resultdir
, self
.pkg
.arch
)
535 if not os
.path
.exists(resultdir
):
536 os
.makedirs(resultdir
)
538 resultfile
= os
.path
.join(resultdir
, self
.pkg
.package_filename
)
539 logging
.info("Saving package to %s" % resultfile
)
541 os
.link(tempfile
, resultfile
)
543 shutil
.copy2(tempfile
, resultfile
)
545 return BinaryPackage(self
.pakfire
, self
.pakfire
.repos
.dummy
, resultfile
)
548 class SourcePackager(Packager
):
549 def create_metafile(self
, datafile
):
550 info
= collections
.defaultdict(lambda: "")
552 # Generic package information including Pakfire information.
554 "pakfire_version" : PAKFIRE_VERSION
,
558 # Include distribution information.
559 info
.update(self
.pakfire
.distro
.info
)
560 info
.update(self
.pkg
.info
)
562 # Size is the size of the (uncompressed) datafile.
563 info
["inst_size"] = os
.path
.getsize(datafile
)
565 # Update package information for string formatting.
566 requires
= [PACKAGE_INFO_DEPENDENCY_LINE
% r
for r
in self
.pkg
.requires
]
568 "groups" : " ".join(self
.pkg
.groups
),
569 "requires" : "\n".join(requires
),
572 # Format description.
573 description
= [PACKAGE_INFO_DESCRIPTION_LINE
% l \
574 for l
in util
.text_wrap(self
.pkg
.description
, length
=80)]
575 info
["description"] = "\n".join(description
)
579 # Package it built right now.
580 "build_time" : int(time
.time()),
581 "build_id" : uuid
.uuid4(),
584 # Arches equals supported arches.
585 info
["arch"] = self
.pkg
.supported_arches
588 # XXX replace this by the payload hash
590 "uuid" : uuid
.uuid4(),
593 metafile
= self
.mktemp()
595 f
= open(metafile
, "w")
596 f
.write(PACKAGE_INFO
% info
)
601 def create_datafile(self
):
602 filename
= self
.mktemp()
603 datafile
= InnerTarFile(filename
, mode
="w")
605 # Add all downloaded files to the package.
606 for file in self
.pkg
.download():
607 datafile
.add(file, "files/%s" % os
.path
.basename(file))
609 # Add all files in the package directory.
610 for file in sorted(self
.pkg
.files
):
611 arcname
= os
.path
.relpath(file, self
.pkg
.path
)
612 datafile
.add(file, arcname
)
618 def run(self
, resultdirs
=[]):
621 logging
.info(_("Building source package %s:") % self
.pkg
.package_filename
)
623 # Add datafile to package.
624 datafile
= self
.create_datafile()
626 # Create filelist out of data.
627 filelist
= self
.create_filelist(datafile
)
630 metafile
= self
.create_metafile(datafile
)
632 # Add files to the tar archive in correct order.
633 self
.add(metafile
, "info")
634 self
.add(filelist
, "filelist")
635 self
.add(datafile
, "data.img")
637 # Build the final tarball.
638 tempfile
= self
.mktemp()
641 for resultdir
in resultdirs
:
642 # XXX sometimes, there has been a None in resultdirs
646 resultdir
= "%s/%s" % (resultdir
, self
.pkg
.arch
)
648 if not os
.path
.exists(resultdir
):
649 os
.makedirs(resultdir
)
651 resultfile
= os
.path
.join(resultdir
, self
.pkg
.package_filename
)
652 logging
.info("Saving package to %s" % resultfile
)
654 os
.link(tempfile
, resultfile
)
656 shutil
.copy2(tempfile
, resultfile
)
658 # Dump package information.
659 pkg
= SourcePackage(self
.pakfire
, self
.pakfire
.repos
.dummy
, tempfile
)
660 for line
in pkg
.dump(long=True).splitlines():