diff options
author | Iikka Eklund <iikka.eklund@qt.io> | 2019-10-11 13:51:02 +0300 |
---|---|---|
committer | Iikka Eklund <iikka.eklund@qt.io> | 2019-10-23 05:54:17 +0000 |
commit | c59aa5718a7bff0bad775a747242a4a21398d04f (patch) | |
tree | cc075ef58f7f57e4b70416287a0c4bbc677d4427 | |
parent | 9c15a6dd587527913068f0ad278a9ef3434819b0 (diff) |
Add script to build a lib against Qt packagev5.14.0-beta2-packagingv5.13.2-packaging
The script downloads qtbase build artifact and compiles
the given source checkout against it.
The build is archived and uploaded to remote disk.
Task-number: QTQAINFRA-3227
Change-Id: I71fa40703c1333b47c9b40c25b096e7434c801fc
Reviewed-by: Jani Heikkinen <jani.heikkinen@qt.io>
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | packaging-tools/bld_lib.py | 232 | ||||
-rwxr-xr-x | packaging-tools/remote_uploader.py | 26 |
3 files changed, 258 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore index 1ded96a18..9773647f7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ Makefile /packaging-tools/qt-bld-dynamic /packaging-tools/qt-install-dynamic /packaging-tools/ifw-pkg +/packaging-tools/qt_pkg +/packaging-tools/lm_bld +/packaging-tools/lm_install_root diff --git a/packaging-tools/bld_lib.py b/packaging-tools/bld_lib.py new file mode 100644 index 000000000..75af490b5 --- /dev/null +++ b/packaging-tools/bld_lib.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the release tools of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:GPL-EXCEPT$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 3 as published by the Free Software +## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import os +import sys +import argparse +import logging +import urllib.request +import tarfile +import platform +import shutil +import glob +import subprocess +import re +from time import gmtime, strftime +from typing import List, Tuple +from urllib.parse import urlparse +from shutil import which +from remote_uploader import RemoteUploader + +LOG_FMT_CI = "%(asctime)s %(levelname)s:%(filename)s:%(lineno)d(%(process)d): %(message)s" +log = logging.getLogger("Bld") +log.setLevel(logging.INFO) +# Unify format of all messages +try: + from rainbow_logging_handler import RainbowLoggingHandler + handler = RainbowLoggingHandler(sys.stderr, color_asctime=(None, None, False)) +except ImportError: + handler = logging.StreamHandler() + +formatter = logging.Formatter(LOG_FMT_CI) +handler.setFormatter(formatter) +log.addHandler(handler) + + +def findFile(searchPath: str, fileName: str) -> str: + for root, dirs, files in os.walk(searchPath): + if fileName in files: + return os.path.join(root, fileName) + assert False, "Unbale to find: {0} from: {1}".format(fileName, searchPath) + + +def collectLibs(searchPath: str) -> List[str]: + for root, dirs, files in os.walk(searchPath): + for dir in dirs: + if dir == "lib": + return [os.path.join(root, dir, x) for x in os.listdir(os.path.join(root, dir))] + assert False, "Unbale to find: 'lib' from: {0}".format(searchPath) + +def parseQtVersion(downloadUrlPath: str) -> str: + regex = re.compile(r'([\d.]+)') + for item in downloadUrlPath.split("/"): + m = regex.search(item) + if m: + return m.groups()[0] + assert False, "Could not parse Qt version number from: {0}".format(downloadUrlPath) + + +def downloadQtPkg(args: argparse.Namespace, currentDir: str) -> Tuple[str, str]: + urlRes = urlparse(args.qtpkg) + assert urlRes.scheme and urlRes.netloc and urlRes.path, "Invalid URL: {0}".format(args.qtpkg) + qtVersion = parseQtVersion(urlRes.path) + + saveAs = os.path.join(currentDir, os.path.basename(urlRes.path)) + if os.path.exists(saveAs): + log.info("Using existing: %s", saveAs) + else: + log.info("Downloading: %s into: %s", args.qtpkg, saveAs) + urllib.request.urlretrieve(args.qtpkg, saveAs) + + return saveAs, qtVersion + + +def extractArchive(saveAs: str, currentDir: str) -> str: + qtDestDir = os.path.join(currentDir, "qt_pkg") + if not os.path.exists(qtDestDir): + os.makedirs(qtDestDir) + log.info("Extracting to: %s", qtDestDir) + if saveAs.endswith("tar.gz"): + with tarfile.open(saveAs, "r:gz") as tar: + tar.extractall(qtDestDir) + elif saveAs.endswith(".7z"): + try: + os.chdir(qtDestDir) + subprocess.check_call(['7z', 'x', saveAs]) + except Exception as e: + log.error("Extracting 7z file failed: %s", str(e)) + raise + finally: + os.chdir(currentDir) + return qtDestDir + + +def build(qtDestDir: str, currentDir: str) -> str: + if platform.system().lower() == "windows": + qmakeToolName = "qmake.exe" + makeToolName = "nmake" + else: + qmakeToolName = "qmake" + makeToolName = "make" + + qmakeTool = findFile(qtDestDir, qmakeToolName) + assert qmakeTool, "Could not find: {0} from: {1}".format(qmakeToolName, qtDestDir) + + # patch + with open(os.path.join(os.path.dirname(qmakeTool), "qt.conf"), "w+") as f: + f.write("[Paths]\n") + f.write("Prefix=..\n") + + proFile = glob.glob(os.path.join(args.src_path, "*.pro")) + assert proFile, "Could not find .pro file(s) from: {0}".format(args.src_path) + proFile = proFile[0] + log.info("Using .pro file: %s", proFile) + + installRootDir = os.path.join(currentDir, "lib_install_root") + shutil.rmtree(installRootDir, ignore_errors=True) + os.makedirs(installRootDir) + + bldDir = os.path.join(currentDir, "lib_bld") + shutil.rmtree(bldDir, ignore_errors=True) # ignore if path did not exist + os.makedirs(bldDir) + + try: + os.chdir(bldDir) + subprocess.check_call([qmakeTool, proFile]) + subprocess.check_call([makeToolName]) + # on windows chhop out the drive letter (e.g. 'C:'" + installRoot = installRootDir[2:] if platform.system().lower() == "windows" else installRootDir + subprocess.check_call([makeToolName, 'install', 'INSTALL_ROOT=' + installRoot]) + except subprocess.CalledProcessError as buildError: + log.error("Failed to build the project: %s", str(buildError)) + raise + except Exception as e: + log.error("Something bad happened: %s", str(e)) + raise + finally: + os.chdir(currentDir) + + return installRootDir + + +def archive(args: argparse.Namespace, installRootDir: str, currentDir: str) -> str: + # strip out drive letter on Windows e.g. 'C:' + srcPath = args.src_path[2:] if platform.system().lower() == "windows" else args.src_path + archivePath = os.path.join(installRootDir, srcPath.lstrip(os.path.sep)) + log.info("Archiving from: %s", archivePath) + + libs = collectLibs(installRootDir) + for lib in libs: + shutil.copy2(lib, archivePath) + + plat = platform.system().lower() + arch = "x86_64" if sys.maxsize > 2**32 else "x86" + artifactsFileName = "artifacts-" + plat + "-" + arch + ".7z" + artifactsFilePath = os.path.join(currentDir, artifactsFileName) + try: + os.chdir(archivePath) + subprocess.check_call(['7z', 'a', '-m0=lzma2', '-mmt=16', artifactsFilePath, '*']) + except Exception as e: + print(e) + raise + finally: + os.chdir(currentDir) + + log.info("Created artifact: %s", artifactsFilePath) + return artifactsFilePath + + +def handleBuild(args: argparse.Namespace) -> None: + currentDir = os.getcwd() + + saveAs, qtVersion = downloadQtPkg(args, currentDir) + qtDestDir = extractArchive(saveAs, currentDir) + installRootDir = build(qtDestDir, currentDir) + artifactsFilePath = archive(args, installRootDir, currentDir) + + remoteUploader = RemoteUploader(False, args.remote_server, args.username, args.remote_base_path, qtVersion, args.project_name) + remoteUploader.initRemoteSnapshotDir(args.build_id) + remoteUploader.copyToRemote(artifactsFilePath, "") + remoteUploader.updateLatestSymlink() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(prog="Helper script to build a lib against qtbase artifact.") + parser.add_argument("--qtpkg", dest="qtpkg", type=str, default=os.getenv("QT_PKG_URL"), help="URL pointing to pre-built Qt bin package.") + parser.add_argument("--src-path", dest="src_path", type=str, default=os.getenv("SRC_PATH"), help="Path to sources") + parser.add_argument("--remote-server", dest="remote_server", type=str, default=os.getenv("PACKAGE_STORAGE_SERVER"), help="Output server for build artifacts") + parser.add_argument("--username", dest="username", type=str, default=os.getenv("PACKAGE_STORAGE_SERVER_USER"), help="Username for the output server") + parser.add_argument("--remote-base-path", dest="remote_base_path", type=str, default=os.getenv("PACKAGE_STORAGE_SERVER_BASE_DIR"), help="Base path for output") + parser.add_argument("--project-name", dest="project_name", type=str, default=os.getenv("PROJECT_NAME"), help="Base path for output") + parser.add_argument("--build-id", dest="build_id", type=str, default=strftime('%Y%m%d%H%M%S', gmtime()), help="Base path for output") + args = parser.parse_args(sys.argv[1:]) + + assert args.qtpkg, "You must define '--qtpkg'!" + assert args.src_path, "You must define '--src-path'!" + assert args.remote_server, "You must define '--remote-server'!" + assert args.username, "You must define '--username'!" + assert args.remote_base_path, "You must define '--remote-base-path'!" + assert args.project_name, "You must define '--project-name'!" + if not which("7z"): + log.error("Could not find '7z' from the system. This tool is needed for notarization. Aborting..") + sys.exit(1) + + handleBuild(args) diff --git a/packaging-tools/remote_uploader.py b/packaging-tools/remote_uploader.py index ec305078a..338f31265 100755 --- a/packaging-tools/remote_uploader.py +++ b/packaging-tools/remote_uploader.py @@ -27,7 +27,16 @@ ## ############################################################################# -import sh +import platform +try: + import sh +except ImportError: + # fallback: emulate the sh API with pbs + import pbs + class Sh(object): + def __getattr__(self, attr): + return pbs.Command(attr) + sh = Sh() class RemoteUploader: @@ -38,6 +47,7 @@ class RemoteUploader: self.ssh = sh.ssh.bake("-o", "GSSAPIAuthentication=no", "-o", "StrictHostKeyChecking=no", remoteServerUserName + '@' + remoteServer) self.remoteLogin = remoteServerUserName + '@' + remoteServer self.remoteTargetBaseDir = remoteBasePath + '/' + projectName + '/' + branch + '/' + self.remoteLatestLink = self.remoteTargetBaseDir + 'latest' self.remoteTargetDir = '' def initRemoteSnapshotDir(self, buildId): @@ -58,7 +68,17 @@ class RemoteUploader: if destDirName: remoteDestination = remoteDestination + '/' + destDirName + '/' print("Copying [{0}] to [{1}]".format(fileName, remoteDestination)) - self.remoteCopy = sh.rsync.bake(fileName, remoteDestination) + copyTool = sh.scp.bake if platform.system().lower() == "windows" else sh.rsync.bake + remoteCopy = copyTool(fileName, remoteDestination) if self.dryRun: return - self.remoteCopy() + remoteCopy() + + def updateLatestSymlink(self, forceUpdate=True): + print("Creating remote symlink: [{0}] -> [{1}]".format(self.remoteLatestLink, self.remoteTargetDir)) + if not self.dryRun: + options = "-sfn" if forceUpdate else "-sn" + try: + self.ssh.ln(options, self.remoteTargetDir, self.remoteLatestLink) + except sh.ErrorReturnCode_1: + print("Symbolic link already exists.") |