From ed049b168117396d2be39d6ce7f678d3475a83d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= <lfrb@collabora.com> Date: Fri, 9 Dec 2016 22:19:27 -0500 Subject: [PATCH] Create cli tool to cross-compile against a specific chroot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Louis-Francis Ratté-Boulianne <lfrb@collabora.com> Reviewed-by: Guillaume Desmottes <guillaume.desmottes@collabora.co.uk> Differential Revision: https://phabricator.apertis.org/D5045 --- doc/man/ade-build.1 | 36 ++++++++ doc/man/ade-configure.1 | 72 +++++++++++++++ doc/man/ade.1 | 15 ++++ tools/ade | 190 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 doc/man/ade-build.1 create mode 100644 doc/man/ade-configure.1 diff --git a/doc/man/ade-build.1 b/doc/man/ade-build.1 new file mode 100644 index 0000000..7cdc643 --- /dev/null +++ b/doc/man/ade-build.1 @@ -0,0 +1,36 @@ +.TH ADE\-BUILD 1 25/11/2016 0.1612.0 Apertis\ Development\ Tools\ Manual + +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l + +.SH NAME + +ade-build \- Build an Apertis project + +.SH SYNOPSIS +.sp +.nf +\fIade build\fR [--verbose] +.fi +.sp +.SH DESCRIPTION +.sp +Build the project for the configured target\&. It basically wraps "make all"\&. +.sp +.SH OPTIONS +.sp +.PP +\fI\-\-verbose\fR +.RS 4 +Command will produce verbose output\&. +.RE +.sp +.SH SEE ALSO + +ade(1) + +.SH COPYRIGHT + +Copyright (c) 2016 Collabora Ltd. diff --git a/doc/man/ade-configure.1 b/doc/man/ade-configure.1 new file mode 100644 index 0000000..be2df92 --- /dev/null +++ b/doc/man/ade-configure.1 @@ -0,0 +1,72 @@ +.TH ADE\-CONFIGURE 1 25/11/2016 0.1612.0 Apertis\ Development\ Tools\ Manual + +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l + +.SH NAME + +ade-configure \- Configure an Apertis project + +.SH SYNOPSIS +.sp +.nf +\fIade configure\fR [--force] [--debug] --simulator [<args>] +\fIade configure\fR [--force] [--debug] [--path=<sysroot-path>] --sysroot=<sysroot-id> [<args>] +\fIade configure\fR [--force] [--debug] [--path=<sysroot-path>] --device=[<user>[:<pass>]@]<host>[:<port>] [<args>] +.fi +.sp +.SH DESCRIPTION +.sp +Configure the project prior to building it\&. +.sp +.SH OPTIONS +.sp +.PP +\fI\-\-force\fR +.RS 4 +Force the project to get configured even if nothing has changed that would +trigger the need to do so\&. +.RE +.PP +\fI\-\-debug\fR +.RS 4 +Enable debug symbols and disable optimizations for easier debugging\&. +.RE +.PP +\fI\-\-path\fR +.RS 4 +Specify the installation path for sysroots\&. By default, the path is +/opt/sysroot\&. +.sp +.RE +.PP +\fI\-\-simulator\fR or \fI\-\-native\fR +.RS 4 +Configure the project to be built with native architecture and run into +the simulator\&. +.sp +.RE +.PP +\fI\-\-sysroot\fR +.RS 4 +Configure the project to be built with the given sysroot +(e\&.g\&. apertis\-16\&.12\-armhf)\&. +.sp +.RE +.PP +\fI\-\-device\fR +.RS 4 +Configure the project to be built for the given target device +(e\&.g\&. user@192\&.168\&.1\&.98)\&. The proper sysroot to use is chosen +according to the device's image version and architecture\&. +.sp +.RE +.SH SEE ALSO + +ade(1) + +.SH COPYRIGHT + +Copyright (c) 2016 Collabora Ltd. diff --git a/doc/man/ade.1 b/doc/man/ade.1 index d7ea803..c3abccf 100644 --- a/doc/man/ade.1 +++ b/doc/man/ade.1 @@ -53,7 +53,22 @@ Manages the set of installed sysroots needed for cross-compilation\&. See \fBade-sysroot(1)\fR .sp .RE +.PP +\fIconfigure\fR +.RS 4 +Configure the project prior to building it\&. +.sp +See \fBade-configure(1)\fR .sp +.RE +.PP +\fIbuild\fR +.RS 4 +Build the project for simulator or target device\&. +.sp +See \fBade-build(1)\fR +.sp +.RE .SH SEE ALSO ade-sysroot(1) diff --git a/tools/ade b/tools/ade index f3329ea..3aa7029 100755 --- a/tools/ade +++ b/tools/ade @@ -14,13 +14,16 @@ import argparse import argcomplete import configparser import glob +import json import logging import os import pathlib import paramiko import re import shutil +import stat import struct +import subprocess import sys import tarfile import tempfile @@ -71,6 +74,19 @@ class InvalidSysrootArchiveError(Exception): return self.message +class InvalidProjectError(Exception): + def __init__(self, message): + self.message = message + + def __str__(self): + return self.message + + +class NotConfiguredError(Exception): + def __init__(self): + pass + + class NotInstalledError(Exception): def __init__(self): pass @@ -80,6 +96,7 @@ class NotSupportedError(Exception): def __init__(self): pass + def is_valid_url(url): result = urlparse(url) return result.scheme and result.netloc and result.path @@ -560,6 +577,123 @@ class SysrootManager: return f +class Project: + + def __init__(self, path=None, bundle_id=None): + self.bundle_id = None + + if not path: + path = os.getcwd() + self.root = self.find_root(path) + self.parse_info() + + # Override project properties if specified + if bundle_id: + self.bundle_id = bundle_id + + self.check_validity() + + def check_validity(self): + if not self.bundle_id: + raise InvalidProjectError("Property 'BundleId' is not specified") + + def find_root(self, path): + p = pathlib.Path(path) + p.resolve() + while str(p) != p.root: + for filename in ['autogen.sh', 'configure']: + if p.joinpath(filename).exists() and \ + p.joinpath(filename).lstat().st_mode & stat.S_IEXEC: + return str(p) + p = p.parent + raise InvalidProjectError("No configuration script found") + + def parse_info(self): + path = os.path.join(self.root, 'SDKConfig', 'ProjectInfo.json') + if os.path.exists(path): + try: + with open(path) as f: + info = json.load(f) + self.bundle_id = info['BundleId'] + except: + pass + + def is_configured(self): + return os.path.exists(os.path.join(self.root, "config.status")) + + def autoreconf(self): + env = os.environ.copy() + env['NOCONFIGURE'] = '1' + args = ["./autogen.sh"] + p = subprocess.Popen(args, cwd=self.root, env=env) + p.wait() + + def configure(self, sysroot, debug=False, force=False, cflags=[], ldflags=[], args=[]): + env = os.environ.copy() + args = ["./configure"] + args += ["--prefix=/Application/" + self.bundle_id] + args += ["--sysconfdir=/var/Applications/" + self.bundle_id + "/etc"] + args += ["--localstatedir=/var/Applications/" + self.bundle_id + "/var"] + + if debug: + cflags += ["-g"] + cflags += ["-O0"] + + if sysroot: + cflags += ["--sysroot=" + sysroot.path] + cflags += ["-I" + os.path.join(sysroot.path, '/usr/include')] + ldflags += ["--sysroot=" + sysroot.path] + args += ["--host=" + TargetTriplet(sysroot.version.arch).triplet] + args += ["--with-sysroot=" + sysroot.path] + self.set_pkg_config_vars(env, sysroot) + + if not os.path.exists(os.path.join(self.root, "configure")): + self.autoreconf() + + env['CFLAGS'] = ' '.join(cflags) + env['LDFLAGS'] = ' '.join(ldflags) + + p = subprocess.Popen(args, cwd=self.root, env=env) + p.wait() + + def build(self, verbose=False): + if not self.is_configured(): + raise NotConfiguredError + args = ["make"] + if verbose: + args += ["V=1"] + p = subprocess.Popen(args, cwd=self.root) + p.wait() + + def set_pkg_config_vars(self, env, sysroot): + def join_paths(paths): + triplet = TargetTriplet(sysroot.version.arch).triplet + return ":".join(paths).replace("${SYSROOT}", sysroot.path) \ + .replace("${TRIPLET}", triplet) + + # Abort is env already contains PKG_CONFIG_LIBDIR + if os.getenv('PKG_CONFIG_LIBDIR'): + return + + pkgconfig_dir = ["${SYSROOT}/usr/lib/${TRIPLET}/pkgconfig", + "${SYSROOT}/usr/share/pkgconfig"] + env['PKG_CONFIG_DIR'] = join_paths(pkgconfig_dir) + + pkgconfig_path = ["${SYSROOT}/usr/lib/${TRIPLET}/pkgconfig", + "${SYSROOT}/usr/share/pkgconfig", + "${SYSROOT}/usr/lib/pkgconfig", + "/usr/lib/${TRIPLET}/pkgconfig", + "/usr/${TRIPLET}/lib/pkgconfig"] + env['PKG_CONFIG_PATH'] = join_paths(pkgconfig_path) + + pkgconfig_libdir = ["${SYSROOT}/usr/lib/${TRIPLET}/pkgconfig", + "${SYSROOT}/usr/share/pkgconfig"] + env['PKG_CONFIG_LIBDIR'] = join_paths(pkgconfig_libdir) + + pkgconfig_sysroot = ["${SYSROOT}"] + env['PKG_CONFIG_SYSROOT_DIR'] = join_paths(pkgconfig_sysroot) + + class Ade: def __init__(self): @@ -578,6 +712,8 @@ class Ade: self.sysroot = None self.device = None + self.bundle_id = None + self.sysroot_version = None self.distro = None self.release = None @@ -587,6 +723,8 @@ class Ade: self.password = '' self.force = False + self.debug = False + self.verbose = False def get_sdk(self): try: @@ -868,6 +1006,38 @@ class Ade: if self.format == 'parseable': print("ImageVersion:{0}".format(self.target.version.get_tag())) + def do_configure(self): + target = self.get_target() + sysroot = None + + if isinstance(target, Sysroot): + sysroot = target + elif isinstance(target, Device): + sdk = self.get_sdk() + version = self.target.version + if sdk and version.is_compatible(sdk.version): + sysroot = None # Native compilation; SDK version matches device image version + else: + sysroot = self.get_sysroot_manager().get_installed(version) + if not sysroot: + self.die("No sysroot currently installed for {0}{1}{2}" + .format(Colors.OKBLUE, version.get_name(), Colors.ENDC)) + + try: + project = Project(os.getcwd(), bundle_id=self.bundle_id) + project.configure(sysroot, debug=self.debug, force=self.force, args=self.args) + except Exception as e: + self.die("Couldn't configure project: {0}".format(e)) + + def do_build(self): + try: + project = Project() + project.build(self.verbose) + except InvalidProjectError as e: + self.die("Invalid project: {0}".format(e)) + except NotConfiguredError: + self.die("Project is not configured; run 'configure' command first") + def info(self, message): if self.format == 'friendly': print(message) @@ -940,8 +1110,26 @@ if __name__ == '__main__': parser = sysroot_subparsers.add_parser('uninstall', help='Uninstall sysroot', parents=[sysroot_id_parser]) + # Configure parser + configure_parser = subparsers.add_parser('configure', help="Configure application") + configure_parser.add_argument('--force', help="Force configuration", action='store_true') + configure_parser.add_argument('--debug', help="Enable debug symbols", action='store_true') + configure_parser.add_argument('--bundle-id', help="Apertis bundle ID (e.g. org.apertis.App)") + group = configure_parser.add_mutually_exclusive_group() + group.add_argument('--simulator', '--native', help="Use simulator as target", action='store_true', dest='simulator') + group.add_argument('--sysroot', help="Use sysroot as target (e.g. apertis-16.09-armhf)") + group.add_argument('--device', help="Use device as target (e.g. user:pass@192.168.1.98)") + configure_parser.add_argument('args', help="Configure options", nargs=argparse.REMAINDER) + + # Build parser + build_parser = subparsers.add_parser('build', help="Build application") + build_parser.add_argument('--verbose', help="Verbose output", action='store_true') + build_parser.add_argument('args', help="Configure options", nargs=argparse.REMAINDER) + argcomplete.autocomplete(root_parser) obj = Ade() - root_parser.parse_args(namespace=obj) + args, extra = root_parser.parse_known_args(namespace=obj) + if hasattr(obj, 'args'): + obj.args += extra obj.run() -- GitLab