alibuild 1.17.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. alibuild-1.17.19.data/scripts/aliBuild +137 -0
  2. alibuild-1.17.19.data/scripts/aliDeps +7 -0
  3. alibuild-1.17.19.data/scripts/aliDoctor +7 -0
  4. alibuild-1.17.19.data/scripts/alienv +344 -0
  5. alibuild-1.17.19.data/scripts/pb +7 -0
  6. alibuild-1.17.19.dist-info/METADATA +78 -0
  7. alibuild-1.17.19.dist-info/RECORD +74 -0
  8. alibuild-1.17.19.dist-info/WHEEL +5 -0
  9. alibuild-1.17.19.dist-info/licenses/LICENSE.md +674 -0
  10. alibuild-1.17.19.dist-info/top_level.txt +5 -0
  11. alibuild_helpers/__init__.py +21 -0
  12. alibuild_helpers/_version.py +21 -0
  13. alibuild_helpers/analytics.py +120 -0
  14. alibuild_helpers/args.py +493 -0
  15. alibuild_helpers/build.py +1209 -0
  16. alibuild_helpers/build_template.sh +314 -0
  17. alibuild_helpers/clean.py +83 -0
  18. alibuild_helpers/cmd.py +154 -0
  19. alibuild_helpers/deps.py +116 -0
  20. alibuild_helpers/doctor.py +195 -0
  21. alibuild_helpers/git.py +104 -0
  22. alibuild_helpers/init.py +103 -0
  23. alibuild_helpers/log.py +132 -0
  24. alibuild_helpers/scm.py +31 -0
  25. alibuild_helpers/sl.py +62 -0
  26. alibuild_helpers/sync.py +693 -0
  27. alibuild_helpers/templating_plugin.py +18 -0
  28. alibuild_helpers/utilities.py +662 -0
  29. alibuild_helpers/workarea.py +179 -0
  30. debian/changelog +11 -0
  31. debian/compat +1 -0
  32. debian/control +14 -0
  33. debian/copyright +10 -0
  34. debian/files +1 -0
  35. debian/rules +7 -0
  36. docs/README.md +1 -0
  37. docs/SUPPORT +3 -0
  38. docs/docs/alice_logo.png +0 -0
  39. docs/docs/deps.png +0 -0
  40. docs/docs/index.md +75 -0
  41. docs/docs/quick.md +89 -0
  42. docs/docs/reference.md +430 -0
  43. docs/docs/stylesheets/extra.css +9 -0
  44. docs/docs/troubleshooting.md +346 -0
  45. docs/docs/user.md +413 -0
  46. docs/mkdocs.yml +37 -0
  47. templates/alibuild_to_please.jnj +63 -0
  48. tests/test_analytics.py +42 -0
  49. tests/test_args.py +119 -0
  50. tests/test_build.py +426 -0
  51. tests/test_clean.py +154 -0
  52. tests/test_cmd.py +73 -0
  53. tests/test_deps.py +79 -0
  54. tests/test_doctor.py +128 -0
  55. tests/test_git.py +48 -0
  56. tests/test_hashing.py +67 -0
  57. tests/test_init.py +103 -0
  58. tests/test_log.py +50 -0
  59. tests/test_packagelist.py +235 -0
  60. tests/test_parseRecipe.py +132 -0
  61. tests/test_sync.py +332 -0
  62. tests/test_utilities.py +383 -0
  63. tests/test_workarea.py +101 -0
  64. tests/testdist/broken1.sh +1 -0
  65. tests/testdist/broken2.sh +1 -0
  66. tests/testdist/broken3.sh +3 -0
  67. tests/testdist/broken4.sh +2 -0
  68. tests/testdist/broken5.sh +2 -0
  69. tests/testdist/broken6.sh +2 -0
  70. tests/testdist/broken7.sh +5 -0
  71. tests/testdist/clobber-initdotsh.sh +4 -0
  72. tests/testdist/defaults-o2.sh +10 -0
  73. tests/testdist/delete-etc.sh +4 -0
  74. tests/testdist/tracking-env.sh +6 -0
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import re
4
+ import sys
5
+ from os.path import exists, abspath, expanduser
6
+ import logging
7
+ from alibuild_helpers.log import debug, error, banner, info, success, warning
8
+ from alibuild_helpers.log import logger
9
+ from alibuild_helpers.utilities import getPackageList, parseDefaults, readDefaults, validateDefaults
10
+ from alibuild_helpers.cmd import getstatusoutput, DockerRunner
11
+ import tempfile
12
+
13
+ def prunePaths(workDir) -> None:
14
+ for x in ["PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH"]:
15
+ if x not in os.environ:
16
+ continue
17
+ workDirEscaped = re.escape("%s" % workDir) + "[^:]*:?"
18
+ os.environ[x] = re.sub(workDirEscaped, "", os.environ[x])
19
+
20
+ def checkPreferSystem(spec, cmd, homebrew_replacement, getstatusoutput_docker):
21
+ if cmd == "false":
22
+ debug("Package %s can only be managed via alibuild.", spec["package"])
23
+ return (1, "")
24
+ cmd = homebrew_replacement + cmd
25
+ err, out = getstatusoutput_docker(cmd)
26
+ if not err:
27
+ success("Package %s will be picked up from the system.", spec["package"])
28
+ for x in out.split("\n"):
29
+ debug("%s: %s", spec["package"], x)
30
+ return (err, "")
31
+
32
+ warning("Package %s cannot be picked up from the system and will be built by aliBuild.\n"
33
+ "This is due to the fact the following script fails:\n\n%s\n\n"
34
+ "with the following output:\n\n%s\n",
35
+ spec["package"], cmd, "\n".join("%s: %s" % (spec["package"], x) for x in out.split("\n")))
36
+ return (err, "")
37
+
38
+ def checkRequirements(spec, cmd, homebrew_replacement, getstatusoutput_docker):
39
+ if cmd == "false":
40
+ debug("Package %s is not a system requirement.", spec["package"])
41
+ return (0, "")
42
+ cmd = homebrew_replacement + cmd
43
+ err, out = getstatusoutput_docker(cmd)
44
+ if not err:
45
+ success("Required package %s will be picked up from the system.", spec["package"])
46
+ debug("%s", cmd)
47
+ for x in out.split("\n"):
48
+ debug("%s: %s", spec["package"], x)
49
+ return (0, "")
50
+ error("Package %s is a system requirement and cannot be found.\n"
51
+ "This is due to the fact that the following script fails:\n\n%s\n"
52
+ "with the following output:\n\n%s\n%s\n",
53
+ spec["package"], cmd,
54
+ "\n".join("%s: %s" % (spec["package"], x) for x in out.split("\n")),
55
+ spec.get("system_requirement_missing"))
56
+ return (err, "")
57
+
58
+ def systemInfo() -> None:
59
+ _,out = getstatusoutput("env")
60
+ debug("Environment:\n%s", out)
61
+ _,out = getstatusoutput("uname -a")
62
+ debug("uname -a: %s", out)
63
+ _,out = getstatusoutput("mount")
64
+ debug("Mounts:\n%s", out)
65
+ _,out = getstatusoutput("df")
66
+ debug("Disk free:\n%s", out)
67
+ for f in ["/etc/lsb-release", "/etc/redhat-release", "/etc/os-release"]:
68
+ err,out = getstatusoutput("cat "+f)
69
+ if not err:
70
+ debug("%s:\n%s", f, out)
71
+
72
+ def doDoctor(args, parser):
73
+ if not exists(args.configDir):
74
+ parser.error("Wrong path to alidist specified: %s" % args.configDir)
75
+
76
+ prunePaths(abspath(args.workDir))
77
+
78
+ if exists(expanduser("~/.rootlogon.C")):
79
+ warning("You have a ~/.rootlogon.C notice that this might"
80
+ " interfere with your environment in hidden ways.\n"
81
+ "Please review it an make sure you are not force loading any library"
82
+ " which might interphere with the rest of the setup.")
83
+ # Decide if we can use homebrew. If not, we replace it with "true" so
84
+ # that we do not get spurious messages on linux
85
+ homebrew_replacement = ""
86
+ with DockerRunner(args.dockerImage, args.docker_extra_args, extra_env={"ALIBUILD_CONFIG_DIR": "/alidist" if args.docker else os.path.abspath(args.configDir)}, extra_volumes=[f"{os.path.abspath(args.configDir)}:/alidist:ro"] if args.docker else []) as getstatusoutput_docker:
87
+ err, output = getstatusoutput_docker("type c++")
88
+ if err:
89
+ warning("Unable to find system compiler.\n"
90
+ "%s\n"
91
+ "Please follow prerequisites:\n"
92
+ "* On RHEL9 compatible systems (e.g. Alma9): https://alice-doc.github.io/alice-analysis-tutorial/building/prereq-alma9.html\n"
93
+ "* On Fedora compatible systems: https://alice-doc.github.io/alice-analysis-tutorial/building/prereq-fedora.html\n"
94
+ "* On Ubuntu compatible systems: https://alice-doc.github.io/alice-analysis-tutorial/building/prereq-ubuntu.html\n"
95
+ "* On macOS: https://alice-doc.github.io/alice-analysis-tutorial/building/prereq-macos.html\n",
96
+ output)
97
+ err, output = getstatusoutput("type git")
98
+ if err:
99
+ error("Unable to find git.\n"
100
+ "%s\n"
101
+ "Please follow prerequisites:\n"
102
+ "* On RHEL9 compatible systems (e.g. Alma9): https://alice-doc.github.io/alice-analysis-tutorial/building/prereq-alma9.html\n"
103
+ "* On Fedora compatible systems: https://alice-doc.github.io/alice-analysis-tutorial/building/prereq-fedora.html\n"
104
+ "* On Ubuntu compatible systems: https://alice-doc.github.io/alice-analysis-tutorial/building/prereq-ubuntu.html\n"
105
+ "* On macOS: https://alice-doc.github.io/alice-analysis-tutorial/building/prereq-macos.html\n",
106
+ output)
107
+ # Decide if we can use homebrew. If not, we replace it with "true" so
108
+ # that we do not get spurious messages on linux
109
+ homebrew_replacement = ""
110
+ err, output = getstatusoutput("which brew")
111
+ if err:
112
+ homebrew_replacement = "brew() { true; }; "
113
+
114
+ logger.setLevel(logging.BANNER)
115
+ if args.debug:
116
+ logger.setLevel(logging.DEBUG)
117
+
118
+ specs = {}
119
+ packages = []
120
+ exitcode = 0
121
+ for p in args.packages:
122
+ path = "%s/%s.sh" % (args.configDir, p.lower())
123
+ if not exists(path):
124
+ error("Cannot find recipe %s for package %s.", path, p)
125
+ exitcode = 1
126
+ continue
127
+ packages.append(p)
128
+ systemInfo()
129
+
130
+ specs = {}
131
+ defaultsReader = lambda : readDefaults(args.configDir, args.defaults, parser.error, args.architecture)
132
+ (err, overrides, taps) = parseDefaults(args.disable, defaultsReader, info)
133
+ if err:
134
+ error("%s", err)
135
+ sys.exit(1)
136
+
137
+ def performValidateDefaults(spec):
138
+ (ok,msg,valid) = validateDefaults(spec, args.defaults)
139
+ if not ok:
140
+ error("%s", msg)
141
+ return (ok,msg,valid)
142
+
143
+ with DockerRunner(args.dockerImage, args.docker_extra_args, extra_env={"ALIBUILD_CONFIG_DIR": "/alidist" if args.docker else os.path.abspath(args.configDir)}, extra_volumes=[f"{os.path.abspath(args.configDir)}:/alidist:ro"] if args.docker else []) as getstatusoutput_docker:
144
+ def performPreferCheckWithTempDir(pkg, cmd):
145
+ with tempfile.TemporaryDirectory(prefix=f"alibuild_prefer_check_{pkg['package']}_") as temp_dir:
146
+ return getstatusoutput_docker(cmd, cwd=temp_dir)
147
+ fromSystem, own, failed, validDefaults = \
148
+ getPackageList(packages = packages,
149
+ specs = specs,
150
+ configDir = args.configDir,
151
+ preferSystem = args.preferSystem,
152
+ noSystem = args.noSystem,
153
+ architecture = args.architecture,
154
+ disable = args.disable,
155
+ defaults = args.defaults,
156
+ performPreferCheck = performPreferCheckWithTempDir,
157
+ performRequirementCheck = performPreferCheckWithTempDir,
158
+ performValidateDefaults = performValidateDefaults,
159
+ overrides = overrides,
160
+ taps = taps,
161
+ log = info)
162
+
163
+ alwaysBuilt = set(x for x in specs) - fromSystem - own - failed
164
+ if alwaysBuilt:
165
+ banner("The following packages will be built by aliBuild because\n"
166
+ " usage of a system version of it is not allowed or supported, by policy:\n\n- %s",
167
+ " \n- ".join(alwaysBuilt))
168
+ if fromSystem:
169
+ banner("The following packages will be picked up from the system:\n\n- %s\n\n"
170
+ "If this is not you want, you have to uninstall / unload them.",
171
+ "\n- ".join(fromSystem))
172
+ if own:
173
+ banner("The following packages will be built by aliBuild because they couldn't be picked up from the system:\n\n"
174
+ "- %s\n\n"
175
+ "This is not a real issue, but it might take longer the first time you invoke aliBuild.\n"
176
+ "Look at the error messages above to get hints on what packages you need to install separately.",
177
+ "\n- ".join(own))
178
+ if failed:
179
+ banner("The following packages are system dependencies and could not be found:\n\n- %s\n\n"
180
+ "Look at the error messages above to get hints on what packages you need to install separately.",
181
+ "\n- ".join(failed))
182
+ exitcode = 1
183
+ if validDefaults and args.defaults not in validDefaults:
184
+ banner("The list of packages cannot be built with the defaults you have specified.\n"
185
+ "List of valid defaults:\n\n- %s\n\n"
186
+ "Use the `--defaults' switch to specify one of them.",
187
+ "\n- ".join(validDefaults))
188
+ exitcode = 2
189
+ if validDefaults is None:
190
+ banner("No valid defaults combination was found for the given list of packages, check your recipes!")
191
+ exitcode = 3
192
+ if exitcode:
193
+ error("There were errors: build cannot be performed if they are not resolved. Check the messages above.")
194
+ sys.exit(exitcode)
195
+
@@ -0,0 +1,104 @@
1
+ from shlex import quote
2
+ from alibuild_helpers.cmd import getstatusoutput
3
+ from alibuild_helpers.log import debug
4
+ from alibuild_helpers.scm import SCM, SCMError
5
+ import os
6
+
7
+ GIT_COMMAND_TIMEOUT_SEC = 120
8
+ """Default value for how many seconds to let any git command execute before being terminated."""
9
+
10
+ GIT_CMD_TIMEOUTS = {
11
+ "clone": 600,
12
+ "checkout": 600
13
+ }
14
+ """Customised timeout for some commands."""
15
+
16
+ def clone_speedup_options():
17
+ """Return a list of options supported by the system git which speed up cloning."""
18
+ for filter_option in ("--filter=tree:0", "--filter=blob:none"):
19
+ _, out = getstatusoutput("LANG=C git clone " + filter_option)
20
+ if "unknown option" not in out and "invalid filter-spec" not in out:
21
+ return [filter_option]
22
+ return []
23
+
24
+
25
+ class Git(SCM):
26
+ name = "Git"
27
+
28
+ def checkedOutCommitName(self, directory):
29
+ return git(("rev-parse", "HEAD"), directory)
30
+
31
+ def branchOrRef(self, directory):
32
+ out = git(("rev-parse", "--abbrev-ref", "HEAD"), directory=directory)
33
+ if out == "HEAD":
34
+ out = git(("rev-parse", "HEAD"), directory)[:10]
35
+ return out
36
+
37
+ def exec(self, *args, **kwargs):
38
+ return git(*args, **kwargs)
39
+
40
+ def parseRefs(self, output):
41
+ return {
42
+ git_ref: git_hash for git_hash, sep, git_ref
43
+ in (line.partition("\t") for line in output.splitlines()) if sep
44
+ }
45
+
46
+ def listRefsCmd(self, repository):
47
+ return ["ls-remote", "--heads", "--tags", repository]
48
+
49
+ def cloneReferenceCmd(self, spec, referenceRepo, usePartialClone):
50
+ cmd = ["clone", "--bare", spec, referenceRepo]
51
+ if usePartialClone:
52
+ cmd.extend(clone_speedup_options())
53
+ return cmd
54
+
55
+ def cloneSourceCmd(self, source, destination, referenceRepo, usePartialClone):
56
+ cmd = ["clone", "-n", source, destination]
57
+ if referenceRepo:
58
+ # If we're building inside a Docker container, we can't refer to the
59
+ # mirror repo directly, since Git uses an absolute path. With
60
+ # "--dissociate", we still copy the objects locally, but we don't refer
61
+ # to them by path.
62
+ cmd.extend(["--dissociate", "--reference", referenceRepo])
63
+ if usePartialClone:
64
+ cmd.extend(clone_speedup_options())
65
+ return cmd
66
+
67
+ def checkoutCmd(self, tag):
68
+ return ["checkout", "-f", tag]
69
+
70
+ def fetchCmd(self, remote, *refs):
71
+ return ["fetch", "-f"] + clone_speedup_options() + [remote, *refs]
72
+
73
+ def setWriteUrlCmd(self, url):
74
+ return ["remote", "set-url", "--push", "origin", url]
75
+
76
+ def diffCmd(self, directory):
77
+ return "cd %s && git diff HEAD && git status --porcelain" % directory
78
+
79
+ def checkUntracked(self, line):
80
+ return line.startswith("?? ")
81
+
82
+
83
+ def git(args, directory=".", check=True, prompt=True):
84
+ lastGitOverride = int(os.environ.get("GIT_CONFIG_COUNT", "0"))
85
+ debug("Executing git %s (in directory %s)", " ".join(args), directory)
86
+ # We can't use git --git-dir=%s/.git or git -C %s here as the former requires
87
+ # that the directory we're inspecting to be the root of a git directory, not
88
+ # just contained in one (and that breaks CI tests), and the latter isn't
89
+ # supported by the git version we have on slc6.
90
+ # Silence cd as shell configuration can cause the new directory to be echoed.
91
+ err, output = getstatusoutput("""\
92
+ set -e +x
93
+ cd {directory} >/dev/null 2>&1
94
+ {prompt_var} {directory_safe_var} git {args}
95
+ """.format(
96
+ directory=quote(directory),
97
+ args=" ".join(map(quote, args)),
98
+ # GIT_TERMINAL_PROMPT is only supported in git 2.3+.
99
+ prompt_var="GIT_TERMINAL_PROMPT=0" if not prompt else "",
100
+ directory_safe_var=f"GIT_CONFIG_COUNT={lastGitOverride+2} GIT_CONFIG_KEY_{lastGitOverride}=safe.directory GIT_CONFIG_VALUE_{lastGitOverride}=$PWD GIT_CONFIG_KEY_{lastGitOverride+1}=gc.auto GIT_CONFIG_VALUE_{lastGitOverride+1}=0" if directory else "",
101
+ ), timeout=GIT_CMD_TIMEOUTS.get(args[0] if len(args) else "*", GIT_COMMAND_TIMEOUT_SEC))
102
+ if check and err != 0:
103
+ raise SCMError("Error {} from git {}: {}".format(err, " ".join(args), output))
104
+ return output if check else (err, output)
@@ -0,0 +1,103 @@
1
+ from alibuild_helpers.git import git, Git
2
+ from alibuild_helpers.utilities import getPackageList, parseDefaults, readDefaults, validateDefaults
3
+ from alibuild_helpers.log import debug, error, warning, banner, info
4
+ from alibuild_helpers.log import dieOnError
5
+ from alibuild_helpers.workarea import updateReferenceRepoSpec
6
+
7
+ from os.path import join
8
+ import os.path as path
9
+ import os
10
+ import sys
11
+
12
+ def parsePackagesDefinition(pkgname):
13
+ return [ dict(zip(["name","ver"], y.split("@")[0:2]))
14
+ for y in [ x+"@" for x in list(filter(lambda y: y, pkgname.split(","))) ] ]
15
+
16
+ def doInit(args):
17
+ assert(args.pkgname != None)
18
+ assert(type(args.dist) == dict)
19
+ assert(sorted(args.dist.keys()) == ["repo", "ver"])
20
+ pkgs = parsePackagesDefinition(args.pkgname)
21
+ assert(type(pkgs) == list)
22
+ if args.dryRun:
23
+ info("This will initialise local checkouts for %s\n"
24
+ "--dry-run / -n specified. Doing nothing.", ",".join(x["name"] for x in pkgs))
25
+ sys.exit(0)
26
+ try:
27
+ path.exists(args.develPrefix) or os.mkdir(args.develPrefix)
28
+ path.exists(args.referenceSources) or os.makedirs(args.referenceSources)
29
+ except OSError as e:
30
+ error("%s", e)
31
+ sys.exit(1)
32
+
33
+ # Fetch recipes first if necessary
34
+ if path.exists(args.configDir):
35
+ warning("using existing recipes from %s", args.configDir)
36
+ else:
37
+ cmd = ["clone", "--origin", "upstream",
38
+ args.dist["repo"] if ":" in args.dist["repo"] else "https://github.com/" + args.dist["repo"]]
39
+ if args.dist["ver"]:
40
+ cmd.extend(["-b", args.dist["ver"]])
41
+ cmd.append(args.configDir)
42
+ git(cmd)
43
+
44
+ # Use standard functions supporting overrides and taps. Ignore all disables
45
+ # and system packages as they are irrelevant in this context
46
+ specs = {}
47
+ defaultsReader = lambda: readDefaults(args.configDir, args.defaults, lambda msg: error("%s", msg), args.architecture)
48
+ (err, overrides, taps) = parseDefaults([], defaultsReader, debug)
49
+ (_,_,_,validDefaults) = getPackageList(packages=[ p["name"] for p in pkgs ],
50
+ specs=specs,
51
+ configDir=args.configDir,
52
+ preferSystem=False,
53
+ noSystem="*",
54
+ architecture="",
55
+ disable=[],
56
+ defaults=args.defaults,
57
+ performPreferCheck=lambda *x, **y: (1, ""),
58
+ performRequirementCheck=lambda *x, **y: (0, ""),
59
+ performValidateDefaults=lambda spec : validateDefaults(spec, args.defaults),
60
+ overrides=overrides,
61
+ taps=taps,
62
+ log=debug)
63
+ dieOnError(validDefaults and args.defaults not in validDefaults,
64
+ "Specified default `%s' is not compatible with the packages you want to build.\n" % args.defaults +
65
+ "Valid defaults:\n\n- " +
66
+ "\n- ".join(sorted(validDefaults)))
67
+
68
+ for p in pkgs:
69
+ spec = specs.get(p["name"])
70
+ spec["is_devel_pkg"] = False
71
+ spec["scm"] = Git()
72
+ dieOnError(spec is None, "cannot find recipe for package %s" % p["name"])
73
+ dest = join(args.develPrefix, spec["package"])
74
+ writeRepo = spec.get("write_repo", spec.get("source"))
75
+ dieOnError(not writeRepo, "package %s has no source field and cannot be developed" % spec["package"])
76
+ if path.exists(dest):
77
+ warning("not cloning %s since it already exists", spec["package"])
78
+ continue
79
+ p["ver"] = p["ver"] if p["ver"] else spec.get("tag", spec["version"])
80
+ debug("cloning %s%s for development", spec["package"], " version "+p["ver"] if p["ver"] else "")
81
+
82
+ updateReferenceRepoSpec(args.referenceSources, spec["package"], spec, True, False)
83
+
84
+ cmd = ["clone", "--origin", "upstream", spec["source"],
85
+ "--reference", join(args.referenceSources, spec["package"].lower())]
86
+ if p["ver"]:
87
+ cmd.extend(["-b", p["ver"]])
88
+ cmd.append(dest)
89
+ git(cmd)
90
+ git(("remote", "set-url", "--push", "upstream", writeRepo), directory=dest)
91
+
92
+ # Make it point relatively to the mirrors for relocation: as per Git specifics, the path has to
93
+ # be relative to the repository's `.git` directory. Don't do it if no common path is found
94
+ repoObjects = os.path.join(os.path.realpath(dest), ".git", "objects")
95
+ refObjects = os.path.join(os.path.realpath(args.referenceSources),
96
+ spec["package"].lower(), "objects")
97
+ repoAltConf = os.path.join(repoObjects, "info", "alternates")
98
+ if len(os.path.commonprefix([repoObjects, refObjects])) > 1:
99
+ with open(repoAltConf, "w") as fil:
100
+ fil.write(os.path.relpath(refObjects, repoObjects) + "\n")
101
+
102
+ banner("Development directory %s created%s", args.develPrefix,
103
+ " for "+", ".join(x["name"].lower() for x in pkgs) if pkgs else "")
@@ -0,0 +1,132 @@
1
+ import logging
2
+ import sys
3
+ import re
4
+ import time
5
+ import datetime
6
+
7
+ def dieOnError(err, msg) -> None:
8
+ if err:
9
+ error("%s", msg)
10
+ sys.exit(1)
11
+
12
+ class LogFormatter(logging.Formatter):
13
+ def __init__(self, fmtstr) -> None:
14
+ self.fmtstr = fmtstr
15
+ self.COLOR_RESET = "\033[m" if sys.stdout.isatty() else ""
16
+ self.LEVEL_COLORS = { logging.WARNING: "\033[4;33m",
17
+ logging.ERROR: "\033[4;31m",
18
+ logging.CRITICAL: "\033[1;37;41m",
19
+ logging.SUCCESS: "\033[1;32m" } if sys.stdout.isatty() else {}
20
+ def format(self, record):
21
+ record.msg = record.msg % record.args
22
+ if record.levelno == logging.BANNER and sys.stdout.isatty():
23
+ lines = record.msg.split("\n")
24
+ return "\n\033[1;34m==>\033[m \033[1m%s\033[m" % lines[0] + \
25
+ "".join("\n \033[1m%s\033[m" % x for x in lines[1:])
26
+ elif record.levelno == logging.INFO or record.levelno == logging.BANNER:
27
+ return record.msg
28
+ return "\n".join(self.fmtstr % {
29
+ "asctime": datetime.datetime.now().strftime("%Y-%m-%d@%H:%M:%S"),
30
+ "levelname": (self.LEVEL_COLORS.get(record.levelno, self.COLOR_RESET) +
31
+ record.levelname + self.COLOR_RESET),
32
+ "message": x,
33
+ } for x in record.msg.split("\n"))
34
+
35
+
36
+ def log_current_package(package, main_package, specs, devel_prefix) -> None:
37
+ """Show PACKAGE as the one currently being processed in future log messages."""
38
+ if logger_handler.level > logging.DEBUG:
39
+ return
40
+ if devel_prefix is not None:
41
+ short_version = devel_prefix
42
+ else:
43
+ short_version = specs[main_package]["commit_hash"]
44
+ if short_version != specs[main_package]["tag"]:
45
+ short_version = short_version[:8]
46
+ logger_handler.setFormatter(LogFormatter(
47
+ "%(asctime)s:%(levelname)s:{}:{}: %(message)s"
48
+ .format(main_package, short_version)
49
+ if package is None else
50
+ "%(asctime)s:%(levelname)s:{}:{}:{}: %(message)s"
51
+ .format(main_package, package, short_version)
52
+ ))
53
+
54
+
55
+ class ProgressPrint:
56
+ def __init__(self, begin_msg="") -> None:
57
+ self.count = -1
58
+ self.lasttime = 0
59
+ self.STAGES = ".", "..", "...", "....", ".....", "....", "...", ".."
60
+ self.begin_msg = begin_msg
61
+ self.percent = -1
62
+
63
+ def __call__(self, txt, *args) -> None:
64
+ if logger.level <= logging.DEBUG or not sys.stdout.isatty():
65
+ debug(txt, *args)
66
+ return
67
+ if time.time() - self.lasttime < 0.5:
68
+ return
69
+ if self.count == -1 and self.begin_msg:
70
+ sys.stderr.write("\033[1;35m==>\033[m " + self.begin_msg)
71
+ txt %= args
72
+ self.erase()
73
+ m = re.search(r"((^|[^0-9])([0-9]{1,2})%|\[([0-9]+)/([0-9]+)\])", txt)
74
+ if m:
75
+ if m.group(3) is not None:
76
+ self.percent = int(m.group(3))
77
+ else:
78
+ num = int(m.group(4))
79
+ den = int(m.group(5))
80
+ if num >= 0 and den > 0:
81
+ self.percent = 100 * num / den
82
+ if self.percent > -1:
83
+ sys.stderr.write(" [%2d%%] " % self.percent)
84
+ self.count = (self.count+1) % len(self.STAGES)
85
+ sys.stderr.write(self.STAGES[self.count])
86
+ self.lasttime = time.time()
87
+ sys.stderr.flush()
88
+
89
+ def erase(self) -> None:
90
+ nerase = len(self.STAGES[self.count]) if self.count > -1 else 0
91
+ if self.percent > -1:
92
+ nerase = nerase + 7
93
+ sys.stderr.write("\b"*nerase+" "*nerase+"\b"*nerase)
94
+ sys.stderr.flush()
95
+
96
+ def end(self, msg="", error=False):
97
+ if self.count == -1:
98
+ return
99
+ self.erase()
100
+ if msg:
101
+ sys.stderr.write(": %s%s\033[m" % ("\033[31m" if error else "\033[32m", msg))
102
+ sys.stderr.write("\n")
103
+ sys.stderr.flush()
104
+
105
+
106
+ # Add loglevel BANNER (same as INFO but with more emphasis on ttys)
107
+ logging.BANNER = 25
108
+ logging.addLevelName(logging.BANNER, "BANNER")
109
+ def log_banner(self, message, *args, **kws):
110
+ if self.isEnabledFor(logging.BANNER):
111
+ self._log(logging.BANNER, message, args, **kws)
112
+ logging.Logger.banner = log_banner
113
+
114
+ # Add loglevel SUCCESS (same as ERROR, but green)
115
+ logging.SUCCESS = 45
116
+ logging.addLevelName(logging.SUCCESS, "SUCCESS")
117
+ def log_success(self, message, *args, **kws):
118
+ if self.isEnabledFor(logging.SUCCESS):
119
+ self._log(logging.SUCCESS, message, args, **kws)
120
+ logging.Logger.success = log_success
121
+
122
+ logger = logging.getLogger('alibuild')
123
+ logger_handler = logging.StreamHandler()
124
+ logger.addHandler(logger_handler)
125
+ logger_handler.setFormatter(LogFormatter("%(levelname)s: %(message)s"))
126
+
127
+ debug = logger.debug
128
+ error = logger.error
129
+ warning = logger.warning
130
+ info = logger.info
131
+ banner = logger.banner
132
+ success = logger.success
@@ -0,0 +1,31 @@
1
+ class SCMError(Exception):
2
+ """Signal that an SCM-related error occurred."""
3
+
4
+
5
+ class SCM(object):
6
+ def checkedOutCommitName(self, directory):
7
+ raise NotImplementedError
8
+ def branchOrRef(self, directory):
9
+ raise NotImplementedError
10
+ def lsRemote(self, remote):
11
+ raise NotImplementedError
12
+ def listRefsCmd(self, repository):
13
+ raise NotImplementedError
14
+ def parseRefs(self, output):
15
+ raise NotImplementedError
16
+ def exec(self, *args, **kwargs):
17
+ raise NotImplementedError
18
+ def checkoutCmd(self, tag):
19
+ raise NotImplementedError
20
+ def fetchCmd(self, remote, *refs):
21
+ raise NotImplementedError
22
+ def cloneReferenceCmd(self, spec, referenceRepo, usePartialClone):
23
+ raise NotImplementedError
24
+ def cloneSourceCmd(self, source, destination, referenceRepo, usePartialClone):
25
+ raise NotImplementedError
26
+ def setWriteUrlCmd(self, url):
27
+ raise NotImplementedError
28
+ def diffCmd(self, directory):
29
+ raise NotImplementedError
30
+ def checkUntracked(self, line):
31
+ raise NotImplementedError
alibuild_helpers/sl.py ADDED
@@ -0,0 +1,62 @@
1
+ from shlex import quote # Python 3.3+
2
+ from alibuild_helpers.cmd import getstatusoutput
3
+ from alibuild_helpers.log import debug
4
+ from alibuild_helpers.scm import SCM, SCMError
5
+
6
+ SL_COMMAND_TIMEOUT_SEC = 120
7
+ """How many seconds to let any sl command execute before being terminated."""
8
+
9
+
10
+ # Sapling is a novel SCM by Meta (i.e. Facebook) that is fully compatible with
11
+ # git, but has a different command line interface. Among the reasons why it's
12
+ # worth supporting it is the ability to handle unnamed branches, the ability to
13
+ # absorb changes to the correct commit without having to explicitly rebase and
14
+ # the integration with github to allow for pull requests to be created from the
15
+ # command line from each commit of a branch.
16
+ class Sapling(SCM):
17
+ name = "Sapling"
18
+
19
+ def checkedOutCommitName(self, directory):
20
+ return sapling(("whereami", ), directory)
21
+
22
+ def branchOrRef(self, directory):
23
+ # Format is <hash>[+] <branch>
24
+ identity = sapling(("identify", ), directory)
25
+ return identity.split(" ")[-1]
26
+
27
+ def exec(self, *args, **kwargs):
28
+ return sapling(*args, **kwargs)
29
+
30
+ def parseRefs(self, output):
31
+ return {
32
+ sl_ref: sl_hash for sl_ref, sep, sl_hash
33
+ in (line.partition("\t") for line in output.splitlines()) if sep
34
+ }
35
+
36
+ def listRefsCmd(self, repository):
37
+ return ["bookmark", "--list", "--remote", "-R", repository]
38
+
39
+ def diffCmd(self, directory):
40
+ return "cd %s && sl diff && sl status" % directory
41
+
42
+ def checkUntracked(self, line):
43
+ return line.startswith("? ")
44
+
45
+
46
+ def sapling(args, directory=".", check=True, prompt=True):
47
+ debug("Executing sl %s (in directory %s)", " ".join(args), directory)
48
+ # We can't use git --git-dir=%s/.git or git -C %s here as the former requires
49
+ # that the directory we're inspecting to be the root of a git directory, not
50
+ # just contained in one (and that breaks CI tests), and the latter isn't
51
+ # supported by the git version we have on slc6.
52
+ # Silence cd as shell configuration can cause the new directory to be echoed.
53
+ err, output = getstatusoutput("""\
54
+ set -e +x
55
+ sl -R {directory} {args}
56
+ """.format(
57
+ directory=quote(directory),
58
+ args=" ".join(map(quote, args)),
59
+ ), timeout=SL_COMMAND_TIMEOUT_SEC)
60
+ if check and err != 0:
61
+ raise SCMError("Error {} from sl {}: {}".format(err, " ".join(args), output))
62
+ return output if check else (err, output)