mirror of
https://github.com/LibreELEC/LibreELEC.tv.git
synced 2025-07-29 13:46:49 +00:00
build: auto remove build dirs
This commit is contained in:
parent
5a57640b89
commit
a11e063083
@ -94,6 +94,7 @@ print_color() {
|
|||||||
CLR_PATCH_DESC) clr_actual="${boldwhite}";;
|
CLR_PATCH_DESC) clr_actual="${boldwhite}";;
|
||||||
CLR_TARGET) clr_actual="${boldwhite}";;
|
CLR_TARGET) clr_actual="${boldwhite}";;
|
||||||
CLR_UNPACK) clr_actual="${boldcyan}";;
|
CLR_UNPACK) clr_actual="${boldcyan}";;
|
||||||
|
CLR_AUTOREMOVE) clr_actual="${boldblue}";;
|
||||||
|
|
||||||
CLR_ENDCOLOR) clr_actual="${endcolor}";;
|
CLR_ENDCOLOR) clr_actual="${endcolor}";;
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ start_multithread_build() {
|
|||||||
fi
|
fi
|
||||||
buildopts+=" --log-combine ${LOGCOMBINE:-always}"
|
buildopts+=" --log-combine ${LOGCOMBINE:-always}"
|
||||||
|
|
||||||
|
[ "${AUTOREMOVE}" = "yes" ] && buildopts+=" --auto-remove"
|
||||||
|
|
||||||
# When building addons, don't halt on error - keep building all packages/addons
|
# When building addons, don't halt on error - keep building all packages/addons
|
||||||
[ "${MTADDONBUILD}" = "yes" ] && buildopts+=" --continue-on-error" || buildopts+=" --halt-on-error"
|
[ "${MTADDONBUILD}" = "yes" ] && buildopts+=" --continue-on-error" || buildopts+=" --halt-on-error"
|
||||||
|
|
||||||
|
12
scripts/autoremove
Executable file
12
scripts/autoremove
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
# Copyright (C) 2019-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
|
. config/options "${1}"
|
||||||
|
|
||||||
|
if [ -d "${PKG_BUILD}" -a "${PKG_SECTION}" != "virtual" ]; then
|
||||||
|
print_color CLR_AUTOREMOVE "AUTOREMOVE ${PKG_BUILD}"
|
||||||
|
echo
|
||||||
|
rm -r "${PKG_BUILD}"
|
||||||
|
fi
|
@ -19,6 +19,8 @@ class LibreELEC_Package:
|
|||||||
self.wants = []
|
self.wants = []
|
||||||
self.wantedby = []
|
self.wantedby = []
|
||||||
|
|
||||||
|
self.unpacks = []
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
s = "%-9s: %s" % ("name", self.name)
|
s = "%-9s: %s" % ("name", self.name)
|
||||||
s = "%s\n%-9s: %s" % (s, "section", self.section)
|
s = "%s\n%-9s: %s" % (s, "section", self.section)
|
||||||
@ -26,6 +28,8 @@ class LibreELEC_Package:
|
|||||||
for t in self.deps:
|
for t in self.deps:
|
||||||
s = "%s\n%-9s: %s" % (s, t, self.deps[t])
|
s = "%s\n%-9s: %s" % (s, t, self.deps[t])
|
||||||
|
|
||||||
|
s = "%s\n%-9s: %s" % (s, "UNPACKS", self.unpacks)
|
||||||
|
|
||||||
s = "%s\n%-9s: %s" % (s, "NEEDS", self.wants)
|
s = "%s\n%-9s: %s" % (s, "NEEDS", self.wants)
|
||||||
s = "%s\n%-9s: %s" % (s, "WANTED BY", self.wantedby)
|
s = "%s\n%-9s: %s" % (s, "WANTED BY", self.wantedby)
|
||||||
|
|
||||||
@ -55,6 +59,10 @@ class LibreELEC_Package:
|
|||||||
if name in self.wantedby:
|
if name in self.wantedby:
|
||||||
self.wantedby.remove(name)
|
self.wantedby.remove(name)
|
||||||
|
|
||||||
|
def addUnpack(self, packages):
|
||||||
|
if packages.strip():
|
||||||
|
self.unpacks = packages.strip().split()
|
||||||
|
|
||||||
def isReferenced(self):
|
def isReferenced(self):
|
||||||
return False if self.wants == [] else True
|
return False if self.wants == [] else True
|
||||||
|
|
||||||
@ -106,7 +114,8 @@ class Node:
|
|||||||
return self.name if self.target == "target" else "%s:%s" % (self.name, self.target)
|
return self.name if self.target == "target" else "%s:%s" % (self.name, self.target)
|
||||||
|
|
||||||
def addEdge(self, node):
|
def addEdge(self, node):
|
||||||
self.edges.append(node)
|
if node not in self.edges:
|
||||||
|
self.edges.append(node)
|
||||||
|
|
||||||
def eprint(*args, **kwargs):
|
def eprint(*args, **kwargs):
|
||||||
print(*args, file=sys.stderr, **kwargs)
|
print(*args, file=sys.stderr, **kwargs)
|
||||||
@ -136,6 +145,8 @@ def initPackage(package):
|
|||||||
for target in ["bootstrap", "init", "host", "target"]:
|
for target in ["bootstrap", "init", "host", "target"]:
|
||||||
pkg.addDependencies(target, package[target])
|
pkg.addDependencies(target, package[target])
|
||||||
|
|
||||||
|
pkg.addUnpack(package["unpack"])
|
||||||
|
|
||||||
return pkg
|
return pkg
|
||||||
|
|
||||||
# Split name:target into components
|
# Split name:target into components
|
||||||
@ -236,7 +247,8 @@ def processPackages(args, packages):
|
|||||||
"bootstrap": "",
|
"bootstrap": "",
|
||||||
"init": "",
|
"init": "",
|
||||||
"host": " ".join(get_packages_by_target("host", args.build)),
|
"host": " ".join(get_packages_by_target("host", args.build)),
|
||||||
"target": " ".join(get_packages_by_target("target", args.build))
|
"target": " ".join(get_packages_by_target("target", args.build)),
|
||||||
|
"unpack": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
packages[pkg["name"]] = initPackage(pkg)
|
packages[pkg["name"]] = initPackage(pkg)
|
||||||
@ -366,9 +378,12 @@ eprint("")
|
|||||||
if args.with_json:
|
if args.with_json:
|
||||||
plan = []
|
plan = []
|
||||||
for step in steps:
|
for step in steps:
|
||||||
|
(pkg_name, target) = split_package(step[1])
|
||||||
plan.append({"task": step[0],
|
plan.append({"task": step[0],
|
||||||
"name": step[1],
|
"name": step[1],
|
||||||
"deps": [d.fqname for d in REQUIRED_PKGS[step[1]].edges]})
|
"section": ALL_PACKAGES[pkg_name].section,
|
||||||
|
"wants": [d.fqname for d in REQUIRED_PKGS[step[1]].edges],
|
||||||
|
"unpacks": ALL_PACKAGES[pkg_name].unpacks if pkg_name in ALL_PACKAGES else []})
|
||||||
|
|
||||||
with open(args.with_json, "w") as out:
|
with open(args.with_json, "w") as out:
|
||||||
print(json.dumps(plan, indent=2, sort_keys=False), file=out)
|
print(json.dumps(plan, indent=2, sort_keys=False), file=out)
|
||||||
|
@ -63,18 +63,71 @@ class Generator:
|
|||||||
self.building = {}
|
self.building = {}
|
||||||
self.built = {}
|
self.built = {}
|
||||||
self.failed = {}
|
self.failed = {}
|
||||||
|
self.removedPackages = {}
|
||||||
|
|
||||||
self.check_no_deps = True
|
self.check_no_deps = True
|
||||||
|
|
||||||
|
# Transform unpack info from package:target to just package - simplifying refcount generation
|
||||||
|
# Create a map for sections, as we don't autoremove "virtual" packages
|
||||||
|
self.unpacks = {}
|
||||||
|
self.sections = {}
|
||||||
|
for job in self.work:
|
||||||
|
(pkg_name, target) = job["name"].split(":")
|
||||||
|
if pkg_name not in self.unpacks:
|
||||||
|
self.unpacks[pkg_name] = job["unpacks"]
|
||||||
|
self.sections[pkg_name] = job["section"]
|
||||||
|
for unpack in job["unpacks"]:
|
||||||
|
if unpack not in self.sections:
|
||||||
|
self.sections[unpack] = "" # don't know section, assume not virtual
|
||||||
|
|
||||||
|
# Count number of times each package is referenced by package:target (including itself) and
|
||||||
|
# then recursively accumulate counts for any other packages that may be referenced
|
||||||
|
# by "PKG_DEPENDS_UNPACK".
|
||||||
|
# Once the refcount is zero for a package, the source directory can be removed.
|
||||||
|
self.refcount = {}
|
||||||
|
for job in self.work:
|
||||||
|
(pkg_name, target) = job["name"].split(":")
|
||||||
|
self.refcount[pkg_name] = self.refcount.get(pkg_name, 0) + 1
|
||||||
|
for pkg_name in job["unpacks"]:
|
||||||
|
self.addRefCounts(pkg_name)
|
||||||
|
|
||||||
def canBuildJob(self, job):
|
def canBuildJob(self, job):
|
||||||
for dep in job["deps"]:
|
for dep in job["wants"]:
|
||||||
if dep not in self.built:
|
if dep not in self.built:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def getPackagesToRemove(self, job):
|
||||||
|
packages = {}
|
||||||
|
|
||||||
|
pkg_name = job["name"].split(":")[0]
|
||||||
|
packages[pkg_name] = True
|
||||||
|
for pkg_name in job["unpacks"]:
|
||||||
|
self.addUnpackPackages(pkg_name, packages)
|
||||||
|
|
||||||
|
for pkg_name in packages:
|
||||||
|
if self.refcount[pkg_name] == 0 and \
|
||||||
|
self.sections[pkg_name] != "virtual" and \
|
||||||
|
pkg_name not in self.removedPackages:
|
||||||
|
yield pkg_name
|
||||||
|
|
||||||
|
def getPackageReferenceCounts(self, job):
|
||||||
|
packages = {}
|
||||||
|
|
||||||
|
pkg_name = job["name"].split(":")[0]
|
||||||
|
packages[pkg_name] = True
|
||||||
|
for pkg_name in job["unpacks"]:
|
||||||
|
self.addUnpackPackages(pkg_name, packages)
|
||||||
|
|
||||||
|
for pkg_name in packages:
|
||||||
|
tokens = ""
|
||||||
|
tokens += "[v]" if self.sections[pkg_name] == "virtual" else ""
|
||||||
|
tokens += "[r]" if pkg_name in self.removedPackages else ""
|
||||||
|
yield("%s:%d%s" % (pkg_name, self.refcount[pkg_name], tokens))
|
||||||
|
|
||||||
def getFirstFailedJob(self, job):
|
def getFirstFailedJob(self, job):
|
||||||
for dep in job["deps"]:
|
for dep in job["wants"]:
|
||||||
if dep in self.failed:
|
if dep in self.failed:
|
||||||
failedjob = self.getFirstFailedJob(self.failed[dep])
|
failedjob = self.getFirstFailedJob(self.failed[dep])
|
||||||
if not failedjob:
|
if not failedjob:
|
||||||
@ -86,7 +139,7 @@ class Generator:
|
|||||||
|
|
||||||
def getAllFailedJobs(self, job):
|
def getAllFailedJobs(self, job):
|
||||||
flist = {}
|
flist = {}
|
||||||
for dep in job["deps"]:
|
for dep in job["wants"]:
|
||||||
if dep in self.failed:
|
if dep in self.failed:
|
||||||
failedjob = self.getFirstFailedJob(self.failed[dep])
|
failedjob = self.getFirstFailedJob(self.failed[dep])
|
||||||
if failedjob:
|
if failedjob:
|
||||||
@ -104,7 +157,7 @@ class Generator:
|
|||||||
# until we're sure there's none left...
|
# until we're sure there's none left...
|
||||||
if self.check_no_deps:
|
if self.check_no_deps:
|
||||||
for i, job in enumerate(self.work):
|
for i, job in enumerate(self.work):
|
||||||
if job["deps"] == []:
|
if job["wants"] == []:
|
||||||
self.building[job["name"]] = True
|
self.building[job["name"]] = True
|
||||||
del self.work[i]
|
del self.work[i]
|
||||||
job["failedjobs"] = self.getAllFailedJobs(job)
|
job["failedjobs"] = self.getAllFailedJobs(job)
|
||||||
@ -133,11 +186,11 @@ class Generator:
|
|||||||
# currently building jobs are complete.
|
# currently building jobs are complete.
|
||||||
def getStallInfo(self):
|
def getStallInfo(self):
|
||||||
for job in self.work:
|
for job in self.work:
|
||||||
for dep in job["deps"]:
|
for dep in job["wants"]:
|
||||||
if dep not in self.building and dep not in self.built:
|
if dep not in self.building and dep not in self.built:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
yield (job["name"], [d for d in job["deps"] if d in self.building])
|
yield (job["name"], [d for d in job["wants"] if d in self.building])
|
||||||
|
|
||||||
def activeJobCount(self):
|
def activeJobCount(self):
|
||||||
return len(self.building)
|
return len(self.building)
|
||||||
@ -157,10 +210,37 @@ class Generator:
|
|||||||
return self.totalJobs
|
return self.totalJobs
|
||||||
|
|
||||||
def completed(self, job):
|
def completed(self, job):
|
||||||
del self.building[job["name"]]
|
|
||||||
self.built[job["name"]] = job
|
self.built[job["name"]] = job
|
||||||
|
del self.building[job["name"]]
|
||||||
|
|
||||||
if job["failed"]:
|
if job["failed"]:
|
||||||
self.failed[job["name"]] = job
|
self.failed[job["name"]] = job
|
||||||
|
else:
|
||||||
|
self.refcount[job["name"].split(":")[0]] -= 1
|
||||||
|
|
||||||
|
for pkg_name in job["unpacks"]:
|
||||||
|
self.delRefCounts(pkg_name)
|
||||||
|
|
||||||
|
def removed(self, pkg_name):
|
||||||
|
self.removedPackages[pkg_name] = True
|
||||||
|
|
||||||
|
def addUnpackPackages(self, pkg_name, packages):
|
||||||
|
packages[pkg_name] = True
|
||||||
|
if pkg_name in self.unpacks:
|
||||||
|
for p in self.unpacks[pkg_name]:
|
||||||
|
self.addUnpackPackages(p, packages)
|
||||||
|
|
||||||
|
def addRefCounts(self, pkg_name):
|
||||||
|
self.refcount[pkg_name] = self.refcount.get(pkg_name, 0) + 1
|
||||||
|
if pkg_name in self.unpacks:
|
||||||
|
for p in self.unpacks[pkg_name]:
|
||||||
|
self.addRefCounts(p)
|
||||||
|
|
||||||
|
def delRefCounts(self, pkg_name):
|
||||||
|
self.refcount[pkg_name] = self.refcount.get(pkg_name, 0) - 1
|
||||||
|
if pkg_name in self.unpacks:
|
||||||
|
for p in self.unpacks[pkg_name]:
|
||||||
|
self.delRefCounts(p)
|
||||||
|
|
||||||
class BuildProcess(threading.Thread):
|
class BuildProcess(threading.Thread):
|
||||||
def __init__(self, slot, maxslot, jobtotal, haltonerror, work, complete):
|
def __init__(self, slot, maxslot, jobtotal, haltonerror, work, complete):
|
||||||
@ -259,8 +339,8 @@ class BuildProcess(threading.Thread):
|
|||||||
|
|
||||||
class Builder:
|
class Builder:
|
||||||
def __init__(self, maxthreadcount, inputfilename, jobglog, loadstats, stats_interval, \
|
def __init__(self, maxthreadcount, inputfilename, jobglog, loadstats, stats_interval, \
|
||||||
haltonerror=True, failimmediately=True, log_burst=True, log_combine="always", bookends=True, \
|
haltonerror=True, failimmediately=True, log_burst=True, log_combine="always", \
|
||||||
debug=False, verbose=False, colors=False):
|
autoremove=False, bookends=True, colors=False, debug=False, verbose=False):
|
||||||
if inputfilename == "-":
|
if inputfilename == "-":
|
||||||
plan = json.load(sys.stdin)
|
plan = json.load(sys.stdin)
|
||||||
else:
|
else:
|
||||||
@ -282,6 +362,7 @@ class Builder:
|
|||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
self.bookends = bookends
|
self.bookends = bookends
|
||||||
|
self.autoremove = autoremove
|
||||||
|
|
||||||
self.colors = (colors == "always" or (colors == "auto" and sys.stderr.isatty()))
|
self.colors = (colors == "always" or (colors == "auto" and sys.stderr.isatty()))
|
||||||
self.color_code = {}
|
self.color_code = {}
|
||||||
@ -345,6 +426,7 @@ class Builder:
|
|||||||
job = self.getCompletedJob()
|
job = self.getCompletedJob()
|
||||||
|
|
||||||
self.writeJobLog(job)
|
self.writeJobLog(job)
|
||||||
|
self.autoRemovePackages(job)
|
||||||
self.processJobOutput(job)
|
self.processJobOutput(job)
|
||||||
self.displayJobStatus(job)
|
self.displayJobStatus(job)
|
||||||
|
|
||||||
@ -538,6 +620,13 @@ class Builder:
|
|||||||
if self.debug:
|
if self.debug:
|
||||||
log_size += len(line)
|
log_size += len(line)
|
||||||
|
|
||||||
|
if "autoremove" in job:
|
||||||
|
for line in job["autoremove"].stdout:
|
||||||
|
print(line, end="")
|
||||||
|
if self.debug:
|
||||||
|
log_size += len(line)
|
||||||
|
job["autoremove"] = None
|
||||||
|
|
||||||
if self.bookends:
|
if self.bookends:
|
||||||
print(">>> %s" % job["name"])
|
print(">>> %s" % job["name"])
|
||||||
|
|
||||||
@ -561,6 +650,29 @@ class Builder:
|
|||||||
j=job, prec=4, width=self.twidth),
|
j=job, prec=4, width=self.twidth),
|
||||||
file=self.joblogfile, flush=True)
|
file=self.joblogfile, flush=True)
|
||||||
|
|
||||||
|
# Remove any source code directories that are no longer required.
|
||||||
|
# Output from the subprocess is either appended to the burst logfile
|
||||||
|
# or is captured for later output to stdout (after the correspnding logfile).
|
||||||
|
def autoRemovePackages(self, job):
|
||||||
|
if self.autoremove:
|
||||||
|
if self.debug:
|
||||||
|
DEBUG("Cleaning Pkg: %s (%s)" % (job["name"], ", ".join(self.generator.getPackageReferenceCounts(job))))
|
||||||
|
|
||||||
|
for pkg_name in self.generator.getPackagesToRemove(job):
|
||||||
|
DEBUG("Removing Pkg: %s" % pkg_name)
|
||||||
|
args = ["%s/%s/autoremove" % (ROOT, SCRIPTS), pkg_name]
|
||||||
|
if job["logfile"]:
|
||||||
|
with open(job["logfile"], "a") as logfile:
|
||||||
|
cmd = subprocess.run(args, cwd=ROOT,
|
||||||
|
stdin=subprocess.PIPE, stdout=logfile, stderr=subprocess.STDOUT,
|
||||||
|
universal_newlines=True, shell=False)
|
||||||
|
else:
|
||||||
|
job["autoremove"] = subprocess.run(args, cwd=ROOT,
|
||||||
|
stdin=subprocess.PIPE, capture_output=True,
|
||||||
|
universal_newlines=True, shell=False)
|
||||||
|
|
||||||
|
self.generator.removed(pkg_name)
|
||||||
|
|
||||||
def startProcesses(self):
|
def startProcesses(self):
|
||||||
for process in self.processes:
|
for process in self.processes:
|
||||||
process.start()
|
process.start()
|
||||||
@ -634,6 +746,9 @@ group.add_argument("--fail-immediately", action="store_true", default=True, \
|
|||||||
group.add_argument("--fail-after-active", action="store_false", dest="fail_immediately", \
|
group.add_argument("--fail-after-active", action="store_false", dest="fail_immediately", \
|
||||||
help="With --halt-on-error, when an error occurs fail after all other active jobs have finished.")
|
help="With --halt-on-error, when an error occurs fail after all other active jobs have finished.")
|
||||||
|
|
||||||
|
parser.add_argument("--auto-remove", action="store_true", default=False, \
|
||||||
|
help="Automatically remove redundant source code directories. Default is disabled.")
|
||||||
|
|
||||||
parser.add_argument("--verbose", action="store_true", default=False, \
|
parser.add_argument("--verbose", action="store_true", default=False, \
|
||||||
help="Output verbose information to stderr.")
|
help="Output verbose information to stderr.")
|
||||||
|
|
||||||
@ -665,7 +780,8 @@ try:
|
|||||||
result = Builder(args.max_procs, args.plan, args.joblog, args.loadstats, args.stats_interval, \
|
result = Builder(args.max_procs, args.plan, args.joblog, args.loadstats, args.stats_interval, \
|
||||||
haltonerror=args.halt_on_error, failimmediately=args.fail_immediately, \
|
haltonerror=args.halt_on_error, failimmediately=args.fail_immediately, \
|
||||||
log_burst=args.log_burst, log_combine=args.log_combine, bookends=args.with_bookends, \
|
log_burst=args.log_burst, log_combine=args.log_combine, bookends=args.with_bookends, \
|
||||||
colors=args.colors, debug=args.debug, verbose=args.verbose).build()
|
autoremove=args.auto_remove, colors=args.colors, \
|
||||||
|
debug=args.debug, verbose=args.verbose).build()
|
||||||
|
|
||||||
if DEBUG_LOG:
|
if DEBUG_LOG:
|
||||||
DEBUG_LOG.close()
|
DEBUG_LOG.close()
|
||||||
|
@ -34,7 +34,8 @@ json_worker() {
|
|||||||
"bootstrap": "${PKG_DEPENDS_BOOTSTRAP}",
|
"bootstrap": "${PKG_DEPENDS_BOOTSTRAP}",
|
||||||
"init": "${PKG_DEPENDS_INIT}",
|
"init": "${PKG_DEPENDS_INIT}",
|
||||||
"host": "${PKG_DEPENDS_HOST}",
|
"host": "${PKG_DEPENDS_HOST}",
|
||||||
"target": "${PKG_DEPENDS_TARGET}"
|
"target": "${PKG_DEPENDS_TARGET}",
|
||||||
|
"unpack": "${PKG_DEPENDS_UNPACK}"
|
||||||
},
|
},
|
||||||
EOF
|
EOF
|
||||||
done
|
done
|
||||||
|
Loading…
x
Reference in New Issue
Block a user