summaryrefslogtreecommitdiff
path: root/talerbuildconfig.py
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-10-14 22:03:24 +0530
committerFlorian Dold <florian.dold@gmail.com>2019-10-14 22:21:56 +0530
commitab8503f11de99b030368d24faf7c8788de9eb84e (patch)
tree49c13e2b45c2ef1d2419bbee64abdb016dadb3df /talerbuildconfig.py
parentc49278458aad2d2bfbf91b3e4ee4dd1650bf9601 (diff)
downloadbuild-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.py275
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