diff options
author | Florian Dold <florian.dold@gmail.com> | 2019-10-14 22:03:24 +0530 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2019-10-14 22:21:56 +0530 |
commit | ab8503f11de99b030368d24faf7c8788de9eb84e (patch) | |
tree | 49c13e2b45c2ef1d2419bbee64abdb016dadb3df /talerbuildconfig.py | |
parent | c49278458aad2d2bfbf91b3e4ee4dd1650bf9601 (diff) | |
download | build-common-ab8503f11de99b030368d24faf7c8788de9eb84e.tar.gz build-common-ab8503f11de99b030368d24faf7c8788de9eb84e.tar.bz2 build-common-ab8503f11de99b030368d24faf7c8788de9eb84e.zip |
build scripts as library
Diffstat (limited to 'talerbuildconfig.py')
-rw-r--r-- | talerbuildconfig.py | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/talerbuildconfig.py b/talerbuildconfig.py new file mode 100644 index 0000000..95f0fa2 --- /dev/null +++ b/talerbuildconfig.py @@ -0,0 +1,275 @@ +# This file is part of TALER +# (C) 2019 GNUnet e.V. +# +# Authors: +# Author: ng0 <ng0@taler.net> +# Author: Florian Dold <dold@taler.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +# LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +# OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +# THIS SOFTWARE. +# +# SPDX-License-Identifier: 0BSD + +from abc import ABC +import argparse +import os +import sys +import logging +from distutils.spawn import find_executable +import subprocess +from dataclasses import dataclass + +""" +This module aims to replicate a small GNU Coding Standards +configure script, taylored at projects in GNU Taler. We hope it +can be of use outside of GNU Taler, hence it is dedicated to the +public domain ('0BSD'). +It takes a couple of arguments on the commandline equivalent to +configure by autotools, in addition some environment variables +xan take precedence over the switches. In the absence of switches, +/usr/local is assumed as the PREFIX. +When all data from tests are gathered, it generates a config.mk +Makefile fragement, which is the processed by a Makefile (usually) in +GNU Make format. +""" + + +class Tool(ABC): + def args(self): + ... + + def check(self, buildconfig): + ... + + +class BuildConfig: + def __init__(self): + # Pairs of (key, value) for config.mk variables + self.make_variables = [] + self.tools = [] + self.tool_results = {} + self.args = None + self.prefix_enabled = False + self.configmk_enabled = False + + def add_tool(self, tool): + if isinstance(tool, Tool): + self.tools.append(tool) + else: + raise Exception("Not a tool instance: " + repr(tool)) + + def _set_tool(self, name, value, version=None): + self.tool_results[name] = (value, version) + + def enable_prefix(self): + """If enabled, process the --prefix argument.""" + self.prefix_enabled = True + + def enable_configmk(self): + """If enabled, output the config.mk makefile fragment.""" + self.configmk_enabled = True + + def run(self): + parser = argparse.ArgumentParser() + if self.prefix_enabled: + parser.add_argument( + "--prefix", + type=str, + default="/usr/local", + help="Directory prefix for installation", + ) + for tool in self.tools: + tool.args(parser) + args = self.args = parser.parse_args() + + for tool in self.tools: + res = tool.check(self) + if not res: + print(f"Error: tool {tool.name} not available") + if hasattr(tool, "hint"): + print(f"Hint: {tool.hint}") + + for tool in self.tools: + path, version = self.tool_results[tool.name] + if version is None: + print(f"found {tool.name} as {path}") + else: + print(f"found {tool.name} as {path} (version {version})") + + if self.configmk_enabled: + with open("config.mk", "w") as f: + f.write("# this makefile fragment is autogenerated by configure.py\n") + if self.prefix_enabled: + f.write(f"prefix = {args.prefix}\n") + for tool in self.tools: + path, version = self.tool_results[tool.name] + f.write(f"{tool.name} = {path}\n") + + +def existence(name): + return find_executable(name) is not None + + +class YarnTool(Tool): + name = "yarn" + description = "The yarn package manager for node" + + def args(self, parser): + parser.add_argument("--with-yarn", action="store") + + def check(self, buildconfig): + yarn_arg = buildconfig.args.with_yarn + if yarn_arg is not None: + buildconfig._set_tool("yarn", yarn_arg) + return True + if existence("yarn"): + p1 = subprocess.run( + ["yarn", "help"], stderr=subprocess.STDOUT, stdout=subprocess.PIPE + ) + if "No such file or directory" in p1.stdout.decode("utf-8"): + if existence("cmdtest"): + buildconfig._warn( + "cmdtest is installed, this can lead to known issues with yarn." + ) + buildconfig._error( + "You seem to have the wrong kind of 'yarn' installed.\n" + "Please remove the conflicting binary before proceeding" + ) + return False + yarn_version = tool_version("yarn --version") + buildconfig._set_tool("yarn", "yarn", yarn_version) + return True + elif existence("yarnpkg"): + yarn_version = tool_version("yarnpkg --version") + buildconfig._set_tool("yarn", "yarnpkg", yarn_version) + return True + return False + + +def tool_version(name): + return subprocess.getstatusoutput(name)[1] + + +class EmscriptenTool: + def args(self, parser): + pass + + def check(self, buildconfig): + if existence("emcc"): + emscripten_version = tool_version("emcc --version") + buildconfig._set_tool("emcc", "emcc", emscripten_version) + return True + return False + + +class PyBabelTool(Tool): + name = "pybabel" + + def args(self, parser): + parser.add_argument( + "--with-pybabel", type=str, help="name of the pybabel executable" + ) + + def check(self, buildconfig): + # No suffix. Would probably be cheaper to do this in + # the dict as well. + if existence("pybabel"): + buildconfig._set_tool("pybabel", "pybabel") + return True + else: + # Has suffix, try suffix. We know the names in advance, + # so use a dictionary and iterate over it. Use enough names + # to safe updating this for another couple of years. + # + # Food for thought: If we only accept python 3.7 or higher, + # is checking pybabel + pybabel-3.[0-9]* too much and could + # be broken down to pybabel + pybabel-3.7 and later names? + version_dict = { + "3.0": "pybabel-3.0", + "3.1": "pybabel-3.1", + "3.2": "pybabel-3.2", + "3.3": "pybabel-3.3", + "3.4": "pybabel-3.4", + "3.5": "pybabel-3.5", + "3.6": "pybabel-3.6", + "3.7": "pybabel-3.7", + "3.8": "pybabel-3.8", + "3.9": "pybabel-3.9", + "4.0": "pybabel-4.0", + } + for value in version_dict.values(): + if existence(value): + buildconfig._set_tool("pybabel", value) + return True + + +class BrowserTool(Tool): + name = "browser" + + def args(self, parser): + parser.add_argument( + "--with-browser", type=str, help="name of your webbrowser executable" + ) + + def check(self, buildconfig): + browser_dict = { + "ice": "icecat", + "ff": "firefox", + "chg": "chrome", + "ch": "chromium", + "o": "opera", + } + if "BROWSER" in os.environ: + buildconfig._set_tool("browser", os.environ["BROWSER"]) + return True + for value in browser_dict.values(): + if existence(value): + buildconfig._set_tool("browser", value) + return True + + +class NodeJsTool(Tool): + name = "node" + hint = "If you are using Ubuntu Linux or Debian Linux, try installing the\nnode-legacy package or symlink node to nodejs." + + def args(self, parser): + pass + + def check(self, buildconfig): + if existence("node") is None: + return False + if ( + subprocess.getstatusoutput( + "node -p 'process.exit(!(/v([0-9]+)/.exec(process.version)[1] >= 4))'" + )[1] + is not "" + ): + buildconfig._warn("your node version is too old, use Node 4.x or newer") + return False + node_version = tool_version("node --version") + buildconfig._set_tool("node", "node", version=node_version) + return True + + +class PosixTool(Tool): + def __init__(self, name): + self.name = name + + def args(self, parser): + pass + + def check(self, buildconfig): + found = existence("find") + if found: + buildconfig._set_tool(self.name, self.name) + return True + return False |