forked from Mirror/pmbootstrap
Add a new action that lists all aports, for which no binary packages exist. Only list packages that can be built for the relevant arch (specified with --arch). This works recursively: when a package can be built for a certain arch, but one of its dependencies (or their depends) can not be built for that arch, then don't list it. This action will be used for the new sr.ht based build infrastructure, to figure out which packages need to be built ahead of time (so we can trigger each of them as single build job). Determining the order of the packages to be built is not determined with pmbootstrap, the serverside code of build.postmarketos.org takes care of that. For testing purposes, a single package can also be specified and the action will list if it can be built for that arch with its dependencies, and what needs to be built exactly. Add pmb/helpers/package.py to hold functions that work on both pmaports and (binary package) repos - in contrary to the existing pmb/helpers/pmaports.py (see previous commit) and pmb/helpers/repo.py, which only work with one of those. Refactoring: * pmb/helpers/pmaports.py: add a get_list() function, which lists all aports and use it instead of writing the same glob loop over and over * add pmb.helpers.pmaports.get(), which finds an APKBUILD and parses it in one step. * rename pmb.build._package.check_arch to ...check_arch_abort to distinguish it from the other check_arch function
209 lines
7.1 KiB
Python
209 lines
7.1 KiB
Python
"""
|
|
Copyright 2018 Oliver Smith
|
|
|
|
This file is part of pmbootstrap.
|
|
|
|
pmbootstrap is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
pmbootstrap is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with pmbootstrap. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
"""
|
|
Functions that work on both (binary package) repos. See also:
|
|
- pmb/helpers/pmaports.py (work on pmaports)
|
|
- pmb/helpers/package.py (work on both)
|
|
"""
|
|
|
|
import os
|
|
import hashlib
|
|
import logging
|
|
import pmb.helpers.http
|
|
import pmb.helpers.run
|
|
|
|
|
|
def hash(url, length=8):
|
|
"""
|
|
Generate the hash, that APK adds to the APKINDEX and apk packages
|
|
in its apk cache folder. It is the "12345678" part in this example:
|
|
"APKINDEX.12345678.tar.gz".
|
|
|
|
:param length: The length of the hash in the output file.
|
|
|
|
See also: official implementation in apk-tools:
|
|
<https://git.alpinelinux.org/cgit/apk-tools/>
|
|
|
|
blob.c: apk_blob_push_hexdump(), "const char *xd"
|
|
apk_defines.h: APK_CACHE_CSUM_BYTES
|
|
database.c: apk_repo_format_cache_index()
|
|
"""
|
|
binary = hashlib.sha1(url.encode("utf-8")).digest()
|
|
xd = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
csum_bytes = int(length / 2)
|
|
|
|
ret = ""
|
|
for i in range(csum_bytes):
|
|
ret += xd[(binary[i] >> 4) & 0xf]
|
|
ret += xd[binary[i] & 0xf]
|
|
|
|
return ret
|
|
|
|
|
|
def urls(args, user_repository=True, postmarketos_mirror=True):
|
|
"""
|
|
Get a list of repository URLs, as they are in /etc/apk/repositories.
|
|
"""
|
|
ret = []
|
|
# Local user repository (for packages compiled with pmbootstrap)
|
|
if user_repository:
|
|
ret.append("/mnt/pmbootstrap-packages")
|
|
|
|
# Upstream postmarketOS binary repository
|
|
if postmarketos_mirror and args.mirror_postmarketos:
|
|
if os.path.exists(args.mirror_postmarketos):
|
|
ret.append("/mnt/postmarketos-mirror")
|
|
else:
|
|
ret.append(args.mirror_postmarketos)
|
|
|
|
# Upstream Alpine Linux repositories
|
|
directories = ["main", "community"]
|
|
if args.alpine_version == "edge":
|
|
directories.append("testing")
|
|
for dir in directories:
|
|
ret.append(args.mirror_alpine + args.alpine_version + "/" + dir)
|
|
return ret
|
|
|
|
|
|
def apkindex_files(args, arch=None):
|
|
"""
|
|
Get a list of outside paths to all resolved APKINDEX.tar.gz files for a
|
|
specific arch.
|
|
:param arch: defaults to native
|
|
"""
|
|
if not arch:
|
|
arch = args.arch_native
|
|
|
|
# Local user repository (for packages compiled with pmbootstrap)
|
|
ret = [args.work + "/packages/" + arch + "/APKINDEX.tar.gz"]
|
|
|
|
# Upstream postmarketOS binary repository
|
|
urls_todo = []
|
|
mirror = args.mirror_postmarketos
|
|
if mirror:
|
|
if os.path.exists(mirror):
|
|
ret.append(mirror + "/" + arch + "/APKINDEX.tar.gz")
|
|
else:
|
|
# Non-local path: treat it like other URLs
|
|
urls_todo.append(mirror)
|
|
|
|
# Resolve the APKINDEX.$HASH.tar.gz files
|
|
urls_todo += urls(args, False, False)
|
|
for url in urls_todo:
|
|
ret.append(args.work + "/cache_apk_" + arch + "/APKINDEX." +
|
|
hash(url) + ".tar.gz")
|
|
|
|
return ret
|
|
|
|
|
|
def update(args, arch=None, force=False, existing_only=False):
|
|
"""
|
|
Download the APKINDEX files for all URLs depending on the architectures.
|
|
|
|
:param arch: * one Alpine architecture name ("x86_64", "armhf", ...)
|
|
* None for all architectures
|
|
:param force: even update when the APKINDEX file is fairly recent
|
|
:param existing_only: only update the APKBUILD files that already exist,
|
|
this is used by "pmbootstrap update"
|
|
|
|
:returns: True when files have been downloaded, False otherwise
|
|
"""
|
|
# Skip in offline mode, only show once
|
|
if args.offline:
|
|
if not args.cache["offline_msg_shown"]:
|
|
logging.info("NOTE: skipping package index update (offline mode)")
|
|
args.cache["offline_msg_shown"] = True
|
|
return False
|
|
|
|
# Architectures and retention time
|
|
architectures = [arch] if arch else pmb.config.build_device_architectures
|
|
retention_hours = pmb.config.apkindex_retention_time
|
|
retention_seconds = retention_hours * 3600
|
|
|
|
# Find outdated APKINDEX files. Formats:
|
|
# outdated: {URL: apkindex_path, ... }
|
|
# outdated_arches: ["armhf", "x86_64", ... ]
|
|
outdated = {}
|
|
outdated_arches = []
|
|
for url in urls(args, False):
|
|
for arch in architectures:
|
|
# APKINDEX file name from the URL
|
|
url_full = url + "/" + arch + "/APKINDEX.tar.gz"
|
|
cache_apk_outside = args.work + "/cache_apk_" + arch
|
|
apkindex = cache_apk_outside + "/APKINDEX." + hash(url) + ".tar.gz"
|
|
|
|
# Find update reason, possibly skip non-existing files
|
|
reason = None
|
|
if not os.path.exists(apkindex):
|
|
if existing_only:
|
|
continue
|
|
reason = "file does not exist yet"
|
|
elif force:
|
|
reason = "forced update"
|
|
elif pmb.helpers.file.is_older_than(apkindex, retention_seconds):
|
|
reason = "older than " + str(retention_hours) + "h"
|
|
if not reason:
|
|
continue
|
|
|
|
# Update outdated and outdated_arches
|
|
logging.debug("APKINDEX outdated (" + reason + "): " + url_full)
|
|
outdated[url_full] = apkindex
|
|
if arch not in outdated_arches:
|
|
outdated_arches.append(arch)
|
|
|
|
# Bail out or show log message
|
|
if not len(outdated):
|
|
return False
|
|
logging.info("Update package index for " + ", ".join(outdated_arches) +
|
|
" (" + str(len(outdated)) + " file(s))")
|
|
|
|
# Download and move to right location
|
|
for url, target in outdated.items():
|
|
temp = pmb.helpers.http.download(args, url, "APKINDEX", False,
|
|
logging.DEBUG)
|
|
target_folder = os.path.dirname(target)
|
|
if not os.path.exists(target_folder):
|
|
pmb.helpers.run.root(args, ["mkdir", "-p", target_folder])
|
|
pmb.helpers.run.root(args, ["cp", temp, target])
|
|
|
|
return True
|
|
|
|
|
|
def alpine_apkindex_path(args, repo="main", arch=None):
|
|
"""
|
|
Get the path to a specific Alpine APKINDEX file on disk and download it if
|
|
necessary.
|
|
|
|
:param repo: Alpine repository name (e.g. "main")
|
|
:param arch: Alpine architecture (e.g. "armhf"), defaults to native arch.
|
|
:returns: full path to the APKINDEX file
|
|
"""
|
|
# Repo sanity check
|
|
if repo not in ["main", "community", "testing", "non-free"]:
|
|
raise RuntimeError("Invalid Alpine repository: " + repo)
|
|
|
|
# Download the file
|
|
update(args, arch)
|
|
|
|
# Find it on disk
|
|
arch = arch or args.arch_native
|
|
repo_link = args.mirror_alpine + args.alpine_version + "/" + repo
|
|
cache_folder = args.work + "/cache_apk_" + arch
|
|
return cache_folder + "/APKINDEX." + hash(repo_link) + ".tar.gz"
|