diff --git a/bin/CraftSetupHelper.py b/bin/CraftSetupHelper.py index 0097fe7b3..6a421583a 100644 --- a/bin/CraftSetupHelper.py +++ b/bin/CraftSetupHelper.py @@ -1,377 +1,382 @@ # -*- coding: utf-8 -*- # Helper script for substitution of paths, independent of cmd or powershell # copyright: # Hannah von Reth import argparse import collections import subprocess import copy +import platform from CraftConfig import * from CraftCore import CraftCore from CraftOS.osutils import OsUtils # The minimum python version for craft please edit here # if you add code that changes this requirement from CraftStandardDirs import CraftStandardDirs, TemporaryUseShortpath MIN_PY_VERSION = (3, 6, 0) def log(msg): if not CraftCore.settings.getboolean("ContinuousIntegration", "Enabled", False): CraftCore.debug.print(msg, sys.stderr) else: CraftCore.log.debug(msg) if sys.version_info[0:3] < MIN_PY_VERSION: log("Error: Python too old!") log("Craft needs at least Python Version %s.%s.%s" % MIN_PY_VERSION) log("Please install it and adapt your CraftSettings.ini") exit(1) +if not platform.machine().endswith("64"): + log(f"Craft requires a 64bit operating system. Your are using: {platform.machine()}") + exit(1) + class SetupHelper(object): CraftVersion = "master" def __init__(self, args=None): self.args = args if CraftCore.settings.getboolean("General", "AllowAnsiColor", False): OsUtils.enableAnsiColors() @staticmethod def _getOutput(command, shell=False): CraftCore.log.debug(f"SetupHelper._getOutput: {command}") p = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell, universal_newlines=True, errors="backslashreplace") out = p.stdout.strip() CraftCore.log.debug(f"SetupHelper._getOutput: return {p.returncode} {out}") return p.returncode, out def run(self): parser = argparse.ArgumentParser() parser.add_argument("--subst", action="store_true") parser.add_argument("--get", action="store_true") parser.add_argument("--print-banner", action="store_true") parser.add_argument("--getenv", action="store_true") parser.add_argument("--setup", action="store_true") parser.add_argument("rest", nargs=argparse.REMAINDER) args = parser.parse_args() if args.subst: self.subst() elif args.get: default = "" if len(args.rest) == 3: default = args.rest[2] CraftCore.log.info(CraftCore.settings.get(args.rest[0], args.rest[1], default)) elif args.print_banner: self.printBanner() elif args.getenv: self.printEnv() elif args.setup: self.subst() self.printEnv() self.printBanner() def checkForEvilApplication(self): blackList = [] if OsUtils.isWin(): blackList += ["sh"] if CraftCore.compiler.isMSVC(): blackList += ["gcc", "g++"] for app in blackList: location = shutil.which(app) if location: location = os.path.dirname(location) if not CraftCore.settings.getboolean("ContinuousIntegration", "Enabled", False): log( f"Found \"{app}\" in your PATH: \"{location}\"\n" f"This application is known to cause problems with your configuration of Craft.\n" f"Please remove it from PATH or manually set a value for PATH in your CraftSettings.ini:\n" f"\n" f"[Environment]\n" f"PATH=" f"\n") else: path = collections.OrderedDict.fromkeys(os.environ["Path"].split(os.path.pathsep)) del path[location] self.addEnvVar("Path", os.path.pathsep.join(path)) def subst(self): if not OsUtils.isWin(): return def _subst(path, drive): if not os.path.exists(path): os.makedirs(path) SetupHelper._getOutput(["subst", CraftCore.settings.get("ShortPath", drive), path]) if CraftCore.settings.getboolean("ShortPath", "Enabled", False): with TemporaryUseShortpath(False): if ("ShortPath", "RootDrive") in CraftCore.settings: _subst(CraftStandardDirs.craftRoot(), "RootDrive") if ("ShortPath", "DownloadDrive") in CraftCore.settings: _subst(CraftStandardDirs.downloadDir(), "DownloadDrive") if ("ShortPath", "GitDrive") in CraftCore.settings: _subst(CraftStandardDirs.gitDir(), "GitDrive") if CraftCore.settings.getboolean("ShortPath", "EnableJunctions", False): with TemporaryUseShortpath(False): if ("ShortPath", "JunctionDrive") in CraftCore.settings: _subst(CraftCore.standardDirs._junctionDir.longPath, "JunctionDrive") def printBanner(self): def printRow(name, value): log(f"{name:20}: {value}") if CraftStandardDirs.isShortPathEnabled(): with TemporaryUseShortpath(False): printRow("Craft Root", CraftStandardDirs.craftRoot()) printRow("Craft", CraftStandardDirs.craftRoot()) printRow("Svn directory", CraftStandardDirs.svnDir()) printRow("Git directory", CraftStandardDirs.gitDir()) else: printRow("Craft", CraftStandardDirs.craftRoot()) printRow("Version", SetupHelper.CraftVersion) printRow("ABI", CraftCore.compiler) printRow("Download directory", CraftStandardDirs.downloadDir()) def addEnvVar(self, key, val): os.environ[key] = val def prependEnvVar(self, key : str, var : str, sep : str=os.path.pathsep) -> None: if not type(var) == list: var = [var] if key in os.environ: env = var + os.environ[key].split(sep) var = list(collections.OrderedDict.fromkeys(env)) val = sep.join(var) CraftCore.log.debug(f"Setting {key}={val}") os.environ[key] = val @staticmethod def stringToEnv(string : str): env = copy.deepcopy(os.environ) for line in string.split("\n"): key, value = line.strip().split("=", 1) env[key] = value return env @staticmethod def _callVCVER(version : int, args : []=None) -> str: if not args: args = [] vswhere = os.path.join(CraftCore.standardDirs.craftBin(), "3rdparty", "vswhere", "vswhere.exe") command = [vswhere, "-property", "installationPath", "-nologo", "-latest"] if version: command += ["-version", f"[{version},{version+1})"] if version < 15: command.append("-legacy") else: if not args: args = ["-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"] return SetupHelper._getOutput(command + args)[1] @staticmethod def getMSVCEnv(version=None, architecture="x86", native=True) -> str: architectures = {"x86": "x86", "x64": "amd64", "x64_cross": "x86_amd64"} args = architectures[architecture] + ("_cross" if not native else "") path = "" if version == 14: # are we using msvc2017 with "VC++ 2015.3 v14.00 (v140) toolset for desktop" path = SetupHelper._callVCVER(15, args=["-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.140"]) if path: args += " -vcvars_ver=14.0" if not path: path = SetupHelper._callVCVER(version) path = os.path.join(path, "VC") if not os.path.exists(os.path.join(path, "vcvarsall.bat")): path = os.path.join(path, "Auxiliary", "Build") path = os.path.join(path, "vcvarsall.bat") if not os.path.isfile(path): log(f"Failed to setup msvc compiler.\n" f"{path} does not exist.") exit(1) status, result = SetupHelper._getOutput(f"\"{path}\" {args} > NUL && set", shell=True) if status != 0: log(f"Failed to setup msvc compiler.\n" f"Command: {result} ") exit(1) return SetupHelper.stringToEnv(result) def getEnv(self): if CraftCore.compiler.isMSVC(): return SetupHelper.getMSVCEnv(CraftCore.compiler.getInternalVersion(), CraftCore.compiler.architecture, CraftCore.compiler.isNative()) elif CraftCore.compiler.isIntel(): architectures = {"x86": "ia32", "x64": "intel64"} programFiles = os.getenv("ProgramFiles(x86)") or os.getenv("ProgramFiles") status, result = SetupHelper._getOutput( "\"%s\\Intel\\Composer XE\\bin\\compilervars.bat\" %s > NUL && set" % ( programFiles, architectures[CraftCore.compiler.architecture]), shell=True) if status != 0: log("Failed to setup intel compiler") exit(1) return SetupHelper.stringToEnv(result) return os.environ def setXDG(self): self.prependEnvVar("XDG_DATA_DIRS", [os.path.join(CraftStandardDirs.craftRoot(), "share")]) if OsUtils.isUnix(): self.prependEnvVar("XDG_CONFIG_DIRS", [os.path.join(CraftStandardDirs.craftRoot(), "etc", "xdg")]) self.addEnvVar("XDG_DATA_HOME", os.path.join(CraftStandardDirs.craftRoot(), "home", os.getenv("USER"), ".local5", "share")) self.addEnvVar("XDG_CONFIG_HOME", os.path.join(CraftStandardDirs.craftRoot(), "home", os.getenv("USER"), ".config")) self.addEnvVar("XDG_CACHE_HOME", os.path.join(CraftStandardDirs.craftRoot(), "home", os.getenv("USER"), ".cache")) def _setupUnix(self): self.prependEnvVar("LD_LIBRARY_PATH", [os.path.join(CraftStandardDirs.craftRoot(), "lib"), os.path.join(CraftStandardDirs.craftRoot(), "lib", "x86_64-linux-gnu")]) if OsUtils.isMac(): self.prependEnvVar("DYLD_LIBRARY_PATH", [os.path.join(CraftStandardDirs.craftRoot(), "lib")]) def _setupWin(self): if not "HOME" in os.environ: self.addEnvVar("HOME", os.getenv("USERPROFILE")) if CraftCore.settings.getboolean("QtSDK", "Enabled", "false"): self.prependEnvVar("PATH", os.path.join(CraftCore.settings.get("QtSDK", "Path"), CraftCore.settings.get("QtSDK", "Version"), CraftCore.settings.get("QtSDK", "Compiler"), "bin")) if CraftCore.compiler.isMinGW(): if not CraftCore.settings.getboolean("QtSDK", "Enabled", "false"): if CraftCore.compiler.isX86(): self.prependEnvVar("PATH", os.path.join(CraftStandardDirs.craftRoot(), "mingw", "bin")) else: self.prependEnvVar("PATH", os.path.join(CraftStandardDirs.craftRoot(), "mingw64", "bin")) else: compilerName = CraftCore.settings.get("QtSDK", "Compiler") compilerMap = {"mingw53_32": "mingw530_32"} self.prependEnvVar("PATH", os.path.join(CraftCore.settings.get("QtSDK", "Path"), "Tools", compilerMap.get(compilerName, compilerName), "bin")) if CraftCore.settings.getboolean("QtSDK", "Enabled", "false"): self.prependEnvVar("PATH", os.path.join(CraftCore.settings.get("QtSDK", "Path"), CraftCore.settings.get("QtSDK", "Version"), CraftCore.settings.get("QtSDK", "Compiler"), "bin")) if CraftCore.compiler.isMinGW(): if not CraftCore.settings.getboolean("QtSDK", "Enabled", "false"): if CraftCore.compiler.isX86(): self.prependEnvVar("PATH", os.path.join(CraftStandardDirs.craftRoot(), "mingw", "bin")) else: self.prependEnvVar("PATH", os.path.join(CraftStandardDirs.craftRoot(), "mingw64", "bin")) else: compilerName = CraftCore.settings.get("QtSDK", "Compiler") compilerMap = {"mingw53_32": "mingw530_32"} self.prependEnvVar("PATH", os.path.join(CraftCore.settings.get("QtSDK", "Path"), "Tools", compilerMap.get(compilerName, compilerName), "bin")) def setupEnvironment(self): for var, value in CraftCore.settings.getSection("Environment"): # set and override existing values # the ini is case insensitive so sections are lowercase.... self.addEnvVar(var.upper(), value) self.prependEnvVar("PATH", os.path.dirname(sys.executable)) os.environ = self.getEnv() self.checkForEvilApplication() self.addEnvVar("KDEROOT", CraftStandardDirs.craftRoot()) if CraftCore.settings.getboolean("Compile", "UseCCache", False): self.addEnvVar("CCACHE_DIR", CraftCore.settings.get("Paths", "CCACHE_DIR", os.path.join(CraftStandardDirs.craftRoot(), "build", "CCACHE"))) if OsUtils.isWin(): self._setupWin() else: self.setXDG() self.prependEnvVar("PKG_CONFIG_PATH", os.path.join(CraftStandardDirs.craftRoot(), "lib", "pkgconfig")) self.prependEnvVar("QT_PLUGIN_PATH", [os.path.join(CraftStandardDirs.craftRoot(), "plugins"), os.path.join(CraftStandardDirs.craftRoot(), "lib", "plugins"), os.path.join(CraftStandardDirs.craftRoot(), "lib64", "plugins"), os.path.join(CraftStandardDirs.craftRoot(), "lib", "x86_64-linux-gnu", "plugins"), os.path.join(CraftStandardDirs.craftRoot(), "lib", "plugin") ]) self.prependEnvVar("QML2_IMPORT_PATH", [os.path.join(CraftStandardDirs.craftRoot(), "qml"), os.path.join(CraftStandardDirs.craftRoot(), "lib", "qml"), os.path.join(CraftStandardDirs.craftRoot(), "lib64", "qml"), os.path.join(CraftStandardDirs.craftRoot(), "lib", "x86_64-linux-gnu", "qml") ]) self.prependEnvVar("QML_IMPORT_PATH", os.environ["QML2_IMPORT_PATH"]) self.setXDG() self.prependEnvVar("PATH", CraftStandardDirs.craftBin()) # make sure thate craftroot bin is the first to look for dlls etc self.prependEnvVar("PATH", os.path.join(CraftStandardDirs.craftRoot(), "bin")) self.prependEnvVar("PATH", os.path.join(CraftStandardDirs.craftRoot(), "dev-utils", "bin")) # add python site packages to pythonpath self.prependEnvVar("PYTHONPATH", os.path.join(CraftStandardDirs.craftRoot(), "lib", "site-packages")) if CraftCore.compiler.isClang(): if OsUtils.isUnix(): self.addEnvVar("CC", "/usr/bin/clang") self.addEnvVar("CXX", "/usr/bin/clang++") else: if CraftCore.compiler.isMSVC(): self.addEnvVar("CC", "clang-cl") self.addEnvVar("CXX", "clang-cl") else: self.addEnvVar("CC", "clang") self.addEnvVar("CXX", "clang") if CraftCore.settings.getboolean("General", "AllowAnsiColor", False): self.addEnvVar("CLICOLOR_FORCE", "1") self.addEnvVar("CLICOLOR", "1") if CraftCore.compiler.isClang() and CraftCore.compiler.isMSVC(): self.prependEnvVar("CFLAGS", "-fcolor-diagnostics", sep=" ") self.prependEnvVar("CXXFLAGS", "-fcolor-diagnostics", sep=" ") elif CraftCore.compiler.isGCCLike(): self.prependEnvVar("CFLAGS", "-fdiagnostics-color=always", sep=" ") self.prependEnvVar("CXXFLAGS", "-fdiagnostics-color=always", sep=" ") if OsUtils.isWin(): os.environ["TERM"] = "xterm" # pretend to be a common smart terminal def printEnv(self): self.setupEnvironment() for key, val in os.environ.items(): if "\n" in val: log(f"Not adding ${key} to environment since it contains " "a newline character and that breaks craftenv.sh") continue if key.startswith("BASH_FUNC_"): continue CraftCore.log.info(f"{key}={val}") @property def version(self): return CraftCore.settings.version if __name__ == '__main__': helper = SetupHelper() helper.run() diff --git a/setup/CraftBootstrap.py b/setup/CraftBootstrap.py index d1082c279..5d50441b0 100644 --- a/setup/CraftBootstrap.py +++ b/setup/CraftBootstrap.py @@ -1,294 +1,297 @@ import argparse import configparser import os import platform import re import shutil import subprocess import sys import urllib.parse import urllib.request +if not platform.machine().endswith("64"): + print(f"Craft requires a 64bit operating system. Your are using: {platform.machine()}") + exit(1) class CraftBootstrap(object): def __init__(self, craftRoot, branch, dryRun): self.craftRoot = craftRoot self.branch = branch self.dryRun = dryRun if not dryRun: with open(os.path.join(craftRoot, f"craft-{branch}", "CraftSettings.ini.template"), "rt+") as ini: self.settings = ini.read().splitlines() else: with open(dryRun, "rt+") as ini: self.settings = ini.read().splitlines() @staticmethod def isWin(): return os.name == 'nt' @staticmethod def isUnix(): return os.name == 'posix' @staticmethod def isFreeBSD(): return CraftBootstrap.isUnix() and platform.system() == 'FreeBSD' @staticmethod def isMac(): return CraftBootstrap.isUnix() and platform.system() == 'Darwin' @staticmethod def isLinux(): return CraftBootstrap.isUnix() and platform.system() == 'Linux' @staticmethod def printProgress(percent): width, _ = shutil.get_terminal_size((80, 20)) width -= 20 # margin times = int(width / 100 * percent) sys.stdout.write("\r[{progress}{space}]{percent}%".format(progress="#" * times, space=" " * (width - times), percent=percent)) sys.stdout.flush() @staticmethod def promptForChoice(title, choices, default=None): if not default: if isinstance(choices[0], tuple): default, _ = choices[0] else: default = choices[0] selection = ", ".join(["[{index}] {value}".format(index=index, value=value[0] if isinstance(value, tuple) else value) for index, value in enumerate(choices)]) promp = "{selection} (Default is {default}): ".format(selection=selection, default=default[0] if isinstance(default, tuple) else default) print() while (True): print(title) choice = input(promp) try: choiceInt = int(choice) except: choiceInt = -1 if choice == "": for choice in choices: if isinstance(choice, tuple): key, val = choice else: key = val = choice if key == default: return val elif choiceInt in range(len(choices)): if isinstance(choices[choiceInt], tuple): return choices[choiceInt][1] else: return choices[choiceInt] @staticmethod def promptShortPath(): drivePatern = re.compile("^[A-Z](:|:\\\\)?$", re.IGNORECASE) def promptDriveLetter(purpose, default): while (True): print(f"Enter drive for {purpose}") drive = input(f"[Possibilities A-Z] (Default is {default}):") if drive == "": return default if drivePatern.match(drive): if len(drive) == 1: return drive + ":" return drive[:2] return {"RootDrive": promptDriveLetter("the build root", "R:"), "GitDrive": promptDriveLetter("the location where the git checkouts are located", "Q:")} def setSettignsValue(self, section, key, value): reKey = re.compile(r"^[\#;]?\s*{key}\s*=.*$".format(key=key), re.IGNORECASE) reSection = re.compile(r"^\[(.*)\]$".format(section=section)) inSection = False for i, line in enumerate(self.settings): sectionMatch = reSection.match(line) if sectionMatch: inSection = sectionMatch.group(1) == section elif inSection and reKey.match(line): self.settings[i] = f"{key} = {value}" return print(f"Unable to locate\n" f"\t[{section}]\n" f"\t{key}") exit(1) def writeSettings(self): if not os.path.isdir(os.path.join(self.craftRoot, "etc")): os.makedirs(os.path.join(self.craftRoot, "etc"), exist_ok=True) if not self.dryRun: with open(os.path.join(self.craftRoot, "etc", "CraftSettings.ini"), "wt+") as out: out.write("\n".join(self.settings)) else: with open(self.dryRun + ".dry_run", "wt+") as out: out.write("\n".join(self.settings)) @staticmethod def downloadFile(url, destdir, filename=None): if not os.path.exists(destdir): os.makedirs(destdir) if not filename: _, _, path, _, _, _ = urllib.parse.urlparse(url) filename = os.path.basename(path) print("Starting to download %s to %s" % (url, os.path.join(destdir, filename))) if os.path.exists(os.path.join(destdir, filename)): return True def dlProgress(count, blockSize, totalSize): if totalSize != -1: percent = int(count * blockSize * 100 / totalSize) CraftBootstrap.printProgress(percent) else: sys.stdout.write(("\r%s bytes downloaded" % (count * blockSize))) sys.stdout.flush() urllib.request.urlretrieve(url, filename=os.path.join(destdir, filename), reporthook=dlProgress) print() return os.path.exists(os.path.join(destdir, filename)) def run(args, command): root = os.path.join(args.prefix, "craft") if not os.path.isdir(root): root = os.path.join(args.prefix, f"craft-{args.branch}") script = os.path.join(root, "bin", "craft.py") command = [sys.executable, script] + command commandStr = " ".join(command) print(f"Execute: {commandStr}") if not args.dry_run: if not subprocess.run(command).returncode == 0: exit(1) def getArchitecture(): if CraftBootstrap.isWin(): return CraftBootstrap.promptForChoice("Select architecture", [("x86", "32"), ("x64", "64")], "x64") else: return 64 if sys.maxsize > 2**32 else 32 def getABI(): if CraftBootstrap.isWin(): platform = "windows" abi, compiler = CraftBootstrap.promptForChoice("Select compiler", [("Mingw-w64", ("mingw", "gcc")), ("Microsoft Visual Studio 2015", ("msvc2015", "cl")), ("Microsoft Visual Studio 2017", ("msvc2017", "cl"))], "Microsoft Visual Studio 2015") abi += f"_{getArchitecture()}" elif CraftBootstrap.isUnix(): if CraftBootstrap.isMac(): platform = "macos" compiler = "clang" else: if CraftBootstrap.isLinux(): platform = "linux" elif CraftBootstrap.isFreeBSD(): platform = "freebsd" compiler = CraftBootstrap.promptForChoice("Select compiler", ["gcc", "clang"]) abi = getArchitecture() return f"{platform}-{abi}-{compiler}" def setUp(args): if not args.dry_run and not os.path.exists(args.prefix): os.makedirs(args.prefix) for d in os.listdir(args.prefix): if d != "downloads":#generated by the windows script print("Error: you are trying to install Craft into an non empty directory") exit(1) print("Welcome to the Craft setup wizard!") abi = getABI() if not args.no_short_path and CraftBootstrap.isWin(): print("Windows has problems with too long commands.") print("For that reason we mount Craft directories to drive letters.") print("It just maps the folder to a drive letter you will assign.") shortPath = CraftBootstrap.promptShortPath() installShortCut = False if CraftBootstrap.isWin(): installShortCut = CraftBootstrap.promptForChoice("Do you want to install a StartMenu entry", [("Yes", True), ("No", False)], default="Yes") if not args.dry_run: CraftBootstrap.downloadFile(f"https://github.com/KDE/craft/archive/{args.branch}.zip", os.path.join(args.prefix, "download"), f"craft-{args.branch}.zip") shutil.unpack_archive(os.path.join(args.prefix, "download", f"craft-{args.branch}.zip"), args.prefix) boot = CraftBootstrap(args.prefix, args.branch, args.dry_run) boot.setSettignsValue("Paths", "Python", os.path.dirname(sys.executable)) boot.setSettignsValue("General", "ABI", abi) py = shutil.which("py") if py: py2 = subprocess.getoutput(f"""{py} -2 -c "import sys; print(sys.executable)" """) if os.path.isfile(py2): boot.setSettignsValue("Paths", "Python27", os.path.dirname(py2)) if CraftBootstrap.isWin(): boot.setSettignsValue("Compile", "MakeProgram", "mingw32-make" if "mingw" in abi else "jom") if not args.no_short_path: boot.setSettignsValue("ShortPath", "Enabled", "True") for key, value in shortPath.items(): boot.setSettignsValue("ShortPath", key, value) else: boot.setSettignsValue("ShortPath", "Enabled", "False") else: boot.setSettignsValue("ShortPath", "Enabled", "False") boot.setSettignsValue("Compile", "MakeProgram", "make") boot.writeSettings() cmd = [] if args.verbose: cmd.append("-vvv") cmd += ["craft"] run(args, cmd) if not args.dry_run: shutil.rmtree(os.path.join(args.prefix, f"craft-{args.branch}")) if installShortCut: run(args, ["craft-startmenu-entry"]) print("Setup complete") print() print("Please run the following command to get started:") path = os.path.join(args.prefix, "craft", "craftenv") if CraftBootstrap.isWin(): print(f" {path}.ps1") else: print(f" source {path}.sh") if __name__ == "__main__": parser = argparse.ArgumentParser(prog="CraftSetupHelper") parser.add_argument("--root", action="store", help="Deprecated: use prefix instead.") parser.add_argument("--prefix", action="store", default=os.getcwd(), help="The installation directory.") parser.add_argument("--branch", action="store", default="master", help="The branch to install") parser.add_argument("--verbose", action="store_true", help="The verbosity.") parser.add_argument("--dry-run", action="store", help="Configure the passed CraftSettings.ini and exit.") parser.add_argument("--no-short-path", action="store_true", default="False", help="Skip short path setup") parser.add_argument("--version", action="version", version="%(prog)s master") args = parser.parse_args() if args.root: args.prefix = args.root setUp(args)