Debian's filesystem layout has a nice advantage over Fedora which is
that it can install non-native packages in the main root filesystem. It
is thus possible to prepare an x86_64 filesystem containing -dev packages
for a foreign architecture, along with a GCC cross compiler.
QEMU has used this technique to facilitate developer build testing of
non-x86 architectures, since few people have access to physical
hardware for most of these architectures. For the same reason it would
be helpful to libvirt developers.
This patch extends the 'dockerfile' command to 'lcitool' so that it
accepts a '-x $ARCH' argument.
$ lcitool dockerfile -x s390x libvirt-debian-9 libvirt
This is only valid when using Debian OS versions.
Signed-off-by: Daniel P. Berrangé <berrange(a)redhat.com>
---
guests/lcitool | 103 ++++++++++++++++++++++++++++++++++++++-
guests/vars/mappings.yml | 7 +++
2 files changed, 108 insertions(+), 2 deletions(-)
diff --git a/guests/lcitool b/guests/lcitool
index d762721..9b723bb 100755
--- a/guests/lcitool
+++ b/guests/lcitool
@@ -90,6 +90,47 @@ class Util:
arch = "x86_64"
return arch
+ @staticmethod
+ def convert_native_arch_to_abi(native_arch):
+ archmap = {
+ "aarch64": "aarch64-linux-gnu",
+ "armv6l": "arm-linux-gnueabi",
+ "armv7l": "arm-linux-gnueabihf",
+ "i686": "i686-linux-gnu",
+ "mips": "mips-linux-gnu",
+ "mipsel": "mipsel-linux-gnu",
+ "mips64el": "mips64el-linux-gnuabi64",
+ "ppc64el": "powerpc64le-linux-gnu",
+ "s390x": "s390x-linux-gnu",
+ "x86_64": "x86_64-linux-gnu",
+ }
+ if native_arch not in archmap:
+ raise Exception("Unsupported architecture {}".format(native_arch))
+ return archmap[native_arch]
+
+ @staticmethod
+ def convert_native_arch_to_deb_arch(native_arch):
+ archmap = {
+ "aarch64": "arm64",
+ "armv6l": "armel",
+ "armv7l": "armhf",
+ "i686": "i386",
+ "mips": "mips",
+ "mipsel": "mipsel",
+ "mips64el": "mips64el",
+ "ppc64el": "ppc64el",
+ "s390x": "s390x",
+ "x86_64": "amd64",
+ }
+ if native_arch not in archmap:
+ raise Exception("Unsupported architecture {}".format(native_arch))
+ return archmap[native_arch]
+
+ @staticmethod
+ def convert_native_arch_to_deb_cross_gcc(native_arch):
+ abi = Util.convert_native_arch_to_abi(native_arch)
+ return "gcc-" + abi
+
class Config:
@@ -341,6 +382,12 @@ class Application:
help="git revision to build (remote/branch)",
)
+ def add_cross_arch_arg(parser):
+ parser.add_argument(
+ "-x", "--cross-arch",
+ help="cross compiler architecture",
+ )
+
installparser = subparsers.add_parser(
"install", help="perform unattended host installation")
installparser.set_defaults(func=self._action_install)
@@ -377,6 +424,7 @@ class Application:
add_hosts_arg(dockerfileparser)
add_projects_arg(dockerfileparser)
+ add_cross_arch_arg(dockerfileparser)
def _execute_playbook(self, playbook, hosts, projects, git_revision):
base = Util.get_base()
@@ -541,6 +589,12 @@ class Application:
if package_format not in ["deb", "rpm"]:
raise Error("Host {} doesn't support
Dockerfiles".format(host))
+ if args.cross_arch:
+ if os_name != "Debian":
+ raise Error("Cannot cross compile on {}".format(os_name))
+ if args.cross_arch == self._native_arch:
+ raise Error("Cross arch {} should differ from native {}".
+ format(args.cross_arch, self._native_arch))
projects = self._projects.expand_pattern(args.projects)
for project in projects:
@@ -553,19 +607,36 @@ class Application:
)
pkgs = {}
+ cross_pkgs = {}
base_keys = ["default", package_format, os_name, os_full]
- keys = base_keys + [self._native_arch + "-" + k for k in base_keys]
+ cross_keys = []
+ if args.cross_arch:
+ keys = base_keys + [args.cross_arch + "-" + k for k in base_keys]
+ cross_keys = ["cross-policy-" + k for k in base_keys]
+ else:
+ keys = base_keys + [self._native_arch + "-" + k for k in
base_keys]
+
# We need to add the base project manually here: the standard
# machinery hides it because it's an implementation detail
for project in projects + ["base"]:
for package in self._projects.get_packages(project):
+ cross_policy = "native"
+ for key in cross_keys:
+ if key in mappings[package]:
+ cross_policy = mappings[package][key]
+ if cross_policy not in ["native", "foreign",
"skip"]:
+ raise Error("Unexpected cross arch policy {} for {}",
+ cross_policy, package)
+
for key in keys:
if key in mappings[package]:
pkgs[package] = mappings[package][key]
if package not in pkgs:
continue
- if pkgs[package] is None:
+ if cross_policy == "foreign" and pkgs[package] is not None:
+ cross_pkgs[package] = pkgs[package]
+ if pkgs[package] is None or cross_policy in ["skip",
"foreign"]:
del pkgs[package]
print("FROM {}".format(facts["docker_base"]))
@@ -573,6 +644,15 @@ class Application:
varmap = {}
varmap["pkgs"] = " \\\n
".join(sorted(set(pkgs.values())))
if package_format == "deb":
+ if args.cross_arch:
+ deb_arch = Util.convert_native_arch_to_deb_arch(args.cross_arch)
+ gcc = Util.convert_native_arch_to_deb_cross_gcc(args.cross_arch)
+ varmap["cross_arch"] = deb_arch
+ pkg_names = [p + ":" + deb_arch for p in cross_pkgs.values()]
+ pkg_names.append(gcc)
+ varmap["cross_pkgs"] = " \\\n
".join(sorted(set(pkg_names)))
+ varmap["cross_abi"] =
Util.convert_native_arch_to_abi(args.cross_arch)
+
sys.stdout.write(textwrap.dedent("""
RUN export DEBIAN_FRONTEND=noninteractive && \\
apt-get update && \\
@@ -582,6 +662,25 @@ class Application:
apt-get autoremove -y && \\
apt-get autoclean -y
""").format(**varmap))
+ if args.cross_arch:
+ # Intentionally a separate RUN command from the above
+ # so that the common packages of all cross-built images
+ # share a docker image layer.
+ sys.stdout.write(textwrap.dedent("""
+ RUN export DEBIAN_FRONTEND=noninteractive && \\
+ dpkg --add-architecture {cross_arch} && \\
+ apt-get update && \\
+ apt-get dist-upgrade -y && \\
+ apt-get install --no-install-recommends -y \\
+ {cross_pkgs} && \\
+ apt-get autoremove -y && \\
+ apt-get autoclean -y
+
+ ENV ABI "{cross_abi}"
+ ENV CONFIGURE_OPTS "--host={cross_abi} \\
+ --target={cross_abi}"
+ ENV PKG_CONFIG_LIBDIR "/usr/lib/{cross_abi}/pkgconfig"
+ """).format(**varmap))
elif package_format == "rpm":
if os_name == "Fedora" and os_version == "Rawhide":
sys.stdout.write(textwrap.dedent("""
diff --git a/guests/vars/mappings.yml b/guests/vars/mappings.yml
index 4d06b83..d7f2b28 100644
--- a/guests/vars/mappings.yml
+++ b/guests/vars/mappings.yml
@@ -58,6 +58,13 @@
# x86_64-deb: libxen-dev
# x86_64-Fedora: xen-devel
#
+# In parallel with this 'cross-arch-XXX:' entries can used to set the
+# installation policy when setting up a cross-architecture build env,
+# taking one of the values:
+#
+# - 'native': use the native architecture package (default if omitted)
+# - 'foreign: use the foreign archtiecture package
+# - 'skip': don't install the package
mappings:
--
2.20.1