blob: bd8402314b42391dae630b3478c4f57b54e799fd [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2017 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Jonathan Maw <jonathan.maw@codethink.co.uk>
"""Dpkg build element
A :mod:`BuildElement <buildstream.buildelement>` implementation for using
dpkg elements
Default Configuration
~~~~~~~~~~~~~~~~~~~~~
The dpkg default configuration:
.. literalinclude:: ../../../buildstream/plugins/elements/dpkg_build.yaml
:language: yaml
Public data
~~~~~~~~~~~
This plugin writes to an element's public data.
split-rules
-----------
This plugin overwrites the element's split-rules with a list of its own
creation, creating a split domain for every package that it detected.
e.g.
.. code:: yaml
public:
split-rules:
foo:
- /sbin/foo
- /usr/bin/bar
bar:
- /etc/quux
dpkg-data
---------
control
'''''''
The control file will be written as raw text into the control field.
e.g.
.. code:: yaml
public:
dpkg-data:
foo:
control: |
Source: foo
Section: blah
Build-depends: bar (>= 1337), baz
...
name
''''
The name of the plugin will be written to the name field.
e.g.
.. code:: yaml
public:
dpkg-data:
foo:
name: foobar
package-scripts
---------------
preinst, postinst, prerm and postrm scripts may be written to the
package if they are detected. They are written as raw text. e.g.
.. code:: yaml
public:
package-scripts:
foo:
preinst: |
#!/usr/bin/bash
/sbin/ldconfig
bar:
postinst: |
#!/usr/bin/bash
/usr/share/fonts/generate_fonts.sh
"""
import filecmp
import os
import re
from buildstream import BuildElement, utils
# Element implementation for the 'dpkg' kind.
class DpkgElement(BuildElement):
def _get_packages(self, sandbox):
controlfile = os.path.join("debian", "control")
controlpath = os.path.join(
sandbox.get_directory(),
self.get_variable('build-root').lstrip(os.sep),
controlfile
)
with open(controlpath) as f:
return re.findall(r"Package:\s*(.+)\n", f.read())
def configure(self, node):
# __original_commands is needed for cache-key generation,
# as commands can be altered during builds and invalidate the key
super().configure(node)
self.__original_commands = dict(self.commands)
def get_unique_key(self):
key = super().get_unique_key()
# Overriding because we change self.commands mid-build, making it
# unsuitable to be included in the cache key.
for domain, cmds in self.__original_commands.items():
key[domain] = cmds
return key
def assemble(self, sandbox):
# Replace <PACKAGES> if no variable was set
packages = self._get_packages(sandbox)
self.commands = dict([
(group, [
c.replace("<PACKAGES>", " ".join(packages)) for c in commands
])
for group, commands in self.commands.items()
])
collectdir = super().assemble(sandbox)
bad_overlaps = set()
new_split_rules = {}
new_dpkg_data = {}
new_package_scripts = {}
for package in packages:
package_path = os.path.join(sandbox.get_directory(),
self.get_variable('build-root').lstrip(os.sep),
'debian', package)
# Exclude DEBIAN files because they're pulled in as public metadata
contents = [x for x in utils.list_relative_paths(package_path)
if x != "." and not x.startswith("DEBIAN")]
new_split_rules[package] = contents
# Check for any overlapping files that are different.
# Since we're storing all these files together, we need to warn
# because clobbering is bad!
for content_file in contents:
for split_package, split_contents in new_split_rules.items():
for split_file in split_contents:
content_file_path = os.path.join(package_path,
content_file.lstrip(os.sep))
split_file_path = os.path.join(os.path.dirname(package_path),
split_package,
split_file.lstrip(os.sep))
if (content_file == split_file and
os.path.isfile(content_file_path) and
not filecmp.cmp(content_file_path, split_file_path)):
bad_overlaps.add(content_file)
# Store /DEBIAN metadata for each package.
# DEBIAN/control goes into bst.dpkg-data.<package>.control
controlpath = os.path.join(package_path, "DEBIAN", "control")
if not os.path.exists(controlpath):
self.error("{}: package {} doesn't have a DEBIAN/control in {}!"
.format(self.name, package, package_path))
with open(controlpath, "r") as f:
controldata = f.read()
new_dpkg_data[package] = {"control": controldata, "name": package}
# DEBIAN/{pre,post}{inst,rm} scripts go into bst.package-scripts.<package>.<script>
scriptfiles = ["preinst", "postinst", "prerm", "postrm"]
for s in scriptfiles:
path = os.path.join(package_path, "DEBIAN", s)
if os.path.exists(path):
if package not in new_package_scripts:
new_package_scripts[package] = {}
with open(path, "r") as f:
data = f.read()
new_package_scripts[package][s] = data
bstdata = self.get_public_data("bst")
bstdata["split-rules"] = new_split_rules
bstdata["dpkg-data"] = new_dpkg_data
if new_package_scripts:
bstdata["package-scripts"] = new_package_scripts
self.set_public_data("bst", bstdata)
if bad_overlaps:
self.warn("Destructive overlaps found in some files!", "\n".join(bad_overlaps))
return collectdir
# Plugin entry point
def setup():
return DpkgElement