GNUnet  0.11.x
talerbuildconfig.py
Go to the documentation of this file.
1 # This file is part of TALER
2 # (C) 2019 GNUnet e.V.
3 #
4 # Authors:
5 # Author: ng0 <ng0@taler.net>
6 # Author: Florian Dold <dold@taler.net>
7 #
8 # Permission to use, copy, modify, and/or distribute this software for any
9 # purpose with or without fee is hereby granted.
10 #
11 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
14 # LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
15 # OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
16 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
17 # ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
18 # THIS SOFTWARE.
19 #
20 # SPDX-License-Identifier: 0BSD
21 
22 from abc import ABC
23 import argparse
24 import os
25 import sys
26 import shlex
27 import logging
28 from distutils.spawn import find_executable
29 import subprocess
30 from dataclasses import dataclass
31 
32 """
33 This module aims to replicate a small GNU Coding Standards
34 configure script, taylored at projects in GNU Taler. We hope it
35 can be of use outside of GNU Taler, hence it is dedicated to the
36 public domain ('0BSD').
37 It takes a couple of arguments on the commandline equivalent to
38 configure by autotools, in addition some environment variables
39 xan take precedence over the switches. In the absence of switches,
40 /usr/local is assumed as the PREFIX.
41 When all data from tests are gathered, it generates a config.mk
42 Makefile fragement, which is the processed by a Makefile (usually) in
43 GNU Make format.
44 """
45 
46 
47 # TODO: We need a smallest version argument.
48 
49 class Tool(ABC):
50  def args(self):
51  ...
52 
53  def check(self, buildconfig):
54  ...
55 
56 
58  def __init__(self):
59  # Pairs of (key, value) for config.mk variables
60  self.make_variables = []
61  self.tools = []
62  self.tool_results = {}
63  self.args = None
64  self.prefix_enabled = False
65  self.configmk_enabled = False
66 
67  def add_tool(self, tool):
68  if isinstance(tool, Tool):
69  self.tools.append(tool)
70  else:
71  raise Exception("Not a tool instance: " + repr(tool))
72 
73  def _set_tool(self, name, value, version=None):
74  self.tool_results[name] = (value, version)
75 
76  def enable_prefix(self):
77  """If enabled, process the --prefix argument."""
78  self.prefix_enabled = True
79 
80  def enable_configmk(self):
81  """If enabled, output the config.mk makefile fragment."""
82  self.configmk_enabled = True
83 
84  def run(self):
85  parser = argparse.ArgumentParser()
86  if self.prefix_enabled:
87  parser.add_argument(
88  "--prefix",
89  type=str,
90  default="/usr/local",
91  help="Directory prefix for installation",
92  )
93  for tool in self.tools:
94  tool.args(parser)
95  args = self.args = parser.parse_args()
96 
97  for tool in self.tools:
98  res = tool.check(self)
99  if not res:
100  print(f"Error: tool {tool.name} not available")
101  if hasattr(tool, "hint"):
102  print(f"Hint: {tool.hint}")
103 
104  for tool in self.tools:
105  path, version = self.tool_results[tool.name]
106  if version is None:
107  print(f"found {tool.name} as {path}")
108  else:
109  print(f"found {tool.name} as {path} (version {version})")
110 
111  if self.configmk_enabled:
112  with open("config.mk", "w") as f:
113  print("writing config.mk")
114  f.write("# this makefile fragment is autogenerated by configure.py\n")
115  if self.prefix_enabled:
116  f.write(f"prefix = {args.prefix}\n")
117  for tool in self.tools:
118  path, version = self.tool_results[tool.name]
119  f.write(f"{tool.name} = {path}\n")
120 
121 
122 def existence(name):
123  return find_executable(name) is not None
124 
125 
126 class YarnTool(Tool):
127  name = "yarn"
128  description = "The yarn package manager for node"
129 
130  def args(self, parser):
131  parser.add_argument("--with-yarn", action="store")
132 
133  def check(self, buildconfig):
134  yarn_arg = buildconfig.args.with_yarn
135  if yarn_arg is not None:
136  buildconfig._set_tool("yarn", yarn_arg)
137  return True
138  if existence("yarn"):
139  p1 = subprocess.run(
140  ["yarn", "help"], stderr=subprocess.STDOUT, stdout=subprocess.PIPE
141  )
142  if "No such file or directory" in p1.stdout.decode("utf-8"):
143  if existence("cmdtest"):
144  buildconfig._warn(
145  "cmdtest is installed, this can lead to known issues with yarn."
146  )
147  buildconfig._error(
148  "You seem to have the wrong kind of 'yarn' installed.\n"
149  "Please remove the conflicting binary before proceeding"
150  )
151  return False
152  yarn_version = tool_version("yarn --version")
153  buildconfig._set_tool("yarn", "yarn", yarn_version)
154  return True
155  elif existence("yarnpkg"):
156  yarn_version = tool_version("yarnpkg --version")
157  buildconfig._set_tool("yarn", "yarnpkg", yarn_version)
158  return True
159  return False
160 
161 
162 def tool_version(name):
163  return subprocess.getstatusoutput(name)[1]
164 
165 
167  def args(self, parser):
168  pass
169 
170  def check(self, buildconfig):
171  if existence("emcc"):
172  emscripten_version = tool_version("emcc --version")
173  buildconfig._set_tool("emcc", "emcc", emscripten_version)
174  return True
175  return False
176 
177 
179  name = "pybabel"
180 
181  def args(self, parser):
182  parser.add_argument(
183  "--with-pybabel", type=str, help="name of the pybabel executable"
184  )
185 
186  def check(self, buildconfig):
187  # No suffix. Would probably be cheaper to do this in
188  # the dict as well. We also need to check the python
189  # version it was build against (TODO).
190  if existence("pybabel"):
191  import babel
192  pybabel_version = babel.__version__
193  buildconfig._set_tool("pybabel", "pybabel", pybabel_version)
194  return True
195  else:
196  # Has suffix, try suffix. We know the names in advance,
197  # so use a dictionary and iterate over it. Use enough names
198  # to safe updating this for another couple of years.
199  #
200  # Food for thought: If we only accept python 3.7 or higher,
201  # is checking pybabel + pybabel-3.[0-9]* too much and could
202  # be broken down to pybabel + pybabel-3.7 and later names?
203  version_dict = {
204  "3.0": "pybabel-3.0",
205  "3.1": "pybabel-3.1",
206  "3.2": "pybabel-3.2",
207  "3.3": "pybabel-3.3",
208  "3.4": "pybabel-3.4",
209  "3.5": "pybabel-3.5",
210  "3.6": "pybabel-3.6",
211  "3.7": "pybabel-3.7",
212  "3.8": "pybabel-3.8",
213  "3.9": "pybabel-3.9",
214  "4.0": "pybabel-4.0",
215  }
216  for key, value in version_dict.items():
217  if existence(value):
218  # FIXME: This version reporting is slightly off
219  # FIXME: and only maps to the suffix.
220  pybabel_version = key
221  buildconfig._set_tool("pybabel", value, pybabel_version)
222  return True
223 
224 
226  # This exists in addition to the files in sh, so that
227  # the Makefiles can use this value instead.
228  name = "python"
229 
230  def args(self, parser):
231  parser.add_argument(
232  "--with-python", type=str, help="name of the python executable"
233  )
234 
235  def check(self, buildconfig):
236  # No suffix. Would probably be cheaper to do this in
237  # the dict as well. We need at least version 3.7.
238  if existence("python") and (shlex.split(subprocess.getstatusoutput("python --version")[1])[1] >= '3.7'):
239  # python might not be python3. It might not even be
240  # python 3.x.
241  python_version = shlex.split(subprocess.getstatusoutput("python --version")[1])[1]
242  if python_version >= '3.7':
243  buildconfig._set_tool("python", "python", python_version)
244  return True
245  else:
246  # Has suffix, try suffix. We know the names in advance,
247  # so use a dictionary and iterate over it. Use enough names
248  # to safe updating this for another couple of years.
249  #
250  # Food for thought: If we only accept python 3.7 or higher,
251  # is checking pybabel + pybabel-3.[0-9]* too much and could
252  # be broken down to pybabel + pybabel-3.7 and later names?
253  version_dict = {
254  "3.7": "python3.7",
255  "3.8": "python3.8",
256  "3.9": "python3.9",
257  "4.0": "python4.0",
258  }
259  for key, value in version_dict.items():
260  if existence(value):
261  python3_version = key
262  buildconfig._set_tool("python", value, python3_version)
263  return True
264 
265 
266 # TODO: Make this really optional, not use a hack ("true").
268  name = "browser"
269 
270  def args(self, parser):
271  parser.add_argument(
272  "--with-browser", type=str, help="name of your webbrowser executable"
273  )
274 
275  def check(self, buildconfig):
276  browser_dict = {
277  "ice": "icecat",
278  "ff": "firefox",
279  "chg": "chrome",
280  "ch": "chromium",
281  "o": "opera",
282  "t": "true"
283  }
284  if "BROWSER" in os.environ:
285  buildconfig._set_tool("browser", os.environ["BROWSER"])
286  return True
287  for value in browser_dict.values():
288  if existence(value):
289  buildconfig._set_tool("browser", value)
290  return True
291 
292 
294  name = "node"
295  hint = "If you are using Ubuntu Linux or Debian Linux, try installing the\nnode-legacy package or symlink node to nodejs."
296 
297  def args(self, parser):
298  pass
299 
300  def check(self, buildconfig):
301  if existence("node") is None:
302  return False
303  if (
304  subprocess.getstatusoutput(
305  "node -p 'process.exit(!(/v([0-9]+)/.exec(process.version)[1] >= 4))'"
306  )[1]
307  != ""
308  ):
309  buildconfig._warn("your node version is too old, use Node 4.x or newer")
310  return False
311  node_version = tool_version("node --version")
312  buildconfig._set_tool("node", "node", version=node_version)
313  return True
314 
315 
317  def __init__(self, name):
318  self.name = name
319 
320  def args(self, parser):
321  pass
322 
323  def check(self, buildconfig):
324  found = existence("find")
325  if found:
326  buildconfig._set_tool(self.name, self.name)
327  return True
328  return False
def check(self, buildconfig)
def check(self, buildconfig)
def check(self, buildconfig)
def check(self, buildconfig)
def check(self, buildconfig)
def _set_tool(self, name, value, version=None)
def check(self, buildconfig)
def check(self, buildconfig)
def check(self, buildconfig)