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.
- alibuild-1.17.19.data/scripts/aliBuild +137 -0
- alibuild-1.17.19.data/scripts/aliDeps +7 -0
- alibuild-1.17.19.data/scripts/aliDoctor +7 -0
- alibuild-1.17.19.data/scripts/alienv +344 -0
- alibuild-1.17.19.data/scripts/pb +7 -0
- alibuild-1.17.19.dist-info/METADATA +78 -0
- alibuild-1.17.19.dist-info/RECORD +74 -0
- alibuild-1.17.19.dist-info/WHEEL +5 -0
- alibuild-1.17.19.dist-info/licenses/LICENSE.md +674 -0
- alibuild-1.17.19.dist-info/top_level.txt +5 -0
- alibuild_helpers/__init__.py +21 -0
- alibuild_helpers/_version.py +21 -0
- alibuild_helpers/analytics.py +120 -0
- alibuild_helpers/args.py +493 -0
- alibuild_helpers/build.py +1209 -0
- alibuild_helpers/build_template.sh +314 -0
- alibuild_helpers/clean.py +83 -0
- alibuild_helpers/cmd.py +154 -0
- alibuild_helpers/deps.py +116 -0
- alibuild_helpers/doctor.py +195 -0
- alibuild_helpers/git.py +104 -0
- alibuild_helpers/init.py +103 -0
- alibuild_helpers/log.py +132 -0
- alibuild_helpers/scm.py +31 -0
- alibuild_helpers/sl.py +62 -0
- alibuild_helpers/sync.py +693 -0
- alibuild_helpers/templating_plugin.py +18 -0
- alibuild_helpers/utilities.py +662 -0
- alibuild_helpers/workarea.py +179 -0
- debian/changelog +11 -0
- debian/compat +1 -0
- debian/control +14 -0
- debian/copyright +10 -0
- debian/files +1 -0
- debian/rules +7 -0
- docs/README.md +1 -0
- docs/SUPPORT +3 -0
- docs/docs/alice_logo.png +0 -0
- docs/docs/deps.png +0 -0
- docs/docs/index.md +75 -0
- docs/docs/quick.md +89 -0
- docs/docs/reference.md +430 -0
- docs/docs/stylesheets/extra.css +9 -0
- docs/docs/troubleshooting.md +346 -0
- docs/docs/user.md +413 -0
- docs/mkdocs.yml +37 -0
- templates/alibuild_to_please.jnj +63 -0
- tests/test_analytics.py +42 -0
- tests/test_args.py +119 -0
- tests/test_build.py +426 -0
- tests/test_clean.py +154 -0
- tests/test_cmd.py +73 -0
- tests/test_deps.py +79 -0
- tests/test_doctor.py +128 -0
- tests/test_git.py +48 -0
- tests/test_hashing.py +67 -0
- tests/test_init.py +103 -0
- tests/test_log.py +50 -0
- tests/test_packagelist.py +235 -0
- tests/test_parseRecipe.py +132 -0
- tests/test_sync.py +332 -0
- tests/test_utilities.py +383 -0
- tests/test_workarea.py +101 -0
- tests/testdist/broken1.sh +1 -0
- tests/testdist/broken2.sh +1 -0
- tests/testdist/broken3.sh +3 -0
- tests/testdist/broken4.sh +2 -0
- tests/testdist/broken5.sh +2 -0
- tests/testdist/broken6.sh +2 -0
- tests/testdist/broken7.sh +5 -0
- tests/testdist/clobber-initdotsh.sh +4 -0
- tests/testdist/defaults-o2.sh +10 -0
- tests/testdist/delete-etc.sh +4 -0
- 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
|
+
|
alibuild_helpers/git.py
ADDED
|
@@ -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)
|
alibuild_helpers/init.py
ADDED
|
@@ -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 "")
|
alibuild_helpers/log.py
ADDED
|
@@ -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
|
alibuild_helpers/scm.py
ADDED
|
@@ -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)
|