forked from Mirror/pmbootstrap
The APKBUILD reference for "provides" [1] is not worded clearly; but after reading it over and over again, my understanding is: * package with provides='foo=1.2' will be automatically installed if user requests installing "foo" * package with provides='foo' (without version) will NOT get automatically installed if user requests installing "foo" For pmbootstrap, this means, that we must not attempt to build a package where the pkgname mentioned in provides matches what we are currently resolving, unless there is an equals sign in the provides entry. Fixes: #1862, pmaports#404 [1] https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#provides says: "List of package names (and optionally version info) this package provides. If package with a version is provided (provides='foo=1.2') apk will consider it as an alternate name and it will automatically consider the package for installation by the alternate name, and conflict with other packages having the same name, or provides. If version is not provided (provides='foo'), apk will consider it as virtual package name. Several package with same non-versioned provides can be installed simultaneously. However, none of them will be installed by default when requested by the virtual name - instead, error message is given and user is asked to choose which package providing the virtual name should be installed."
221 lines
7.7 KiB
Python
221 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Copyright 2019 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 only on pmaports. See also:
|
|
- pmb/helpers/repo.py (only work on binary package repos)
|
|
- pmb/helpers/package.py (work on both)
|
|
"""
|
|
|
|
import glob
|
|
import logging
|
|
import os
|
|
|
|
import pmb.parse
|
|
|
|
|
|
def get_list(args):
|
|
""" :returns: list of all pmaport pkgnames (["hello-world", ...]) """
|
|
ret = []
|
|
for apkbuild in glob.glob(args.aports + "/*/*/APKBUILD"):
|
|
ret.append(os.path.basename(os.path.dirname(apkbuild)))
|
|
ret.sort()
|
|
return ret
|
|
|
|
|
|
def guess_main_dev(args, subpkgname):
|
|
"""
|
|
Check if a package without "-dev" at the end exists in pmaports or not, and
|
|
log the appropriate message. Don't call this function directly, use
|
|
guess_main() instead.
|
|
|
|
:param subpkgname: subpackage name, must end in "-dev"
|
|
:returns: full path to the pmaport or None
|
|
"""
|
|
pkgname = subpkgname[:-4]
|
|
paths = glob.glob(args.aports + "/*/" + pkgname)
|
|
if paths:
|
|
logging.debug(subpkgname + ": guessed to be a subpackage of " +
|
|
pkgname + " (just removed '-dev')")
|
|
return paths[0]
|
|
|
|
logging.debug(subpkgname + ": guessed to be a subpackage of " + pkgname +
|
|
", which we can't find in pmaports, so it's probably in"
|
|
" Alpine")
|
|
return None
|
|
|
|
|
|
def guess_main(args, subpkgname):
|
|
"""
|
|
Find the main package by assuming it is a prefix of the subpkgname.
|
|
We do that, because in some APKBUILDs the subpkgname="" variable gets
|
|
filled with a shell loop and the APKBUILD parser in pmbootstrap can't
|
|
parse this right. (Intentionally, we don't want to implement a full shell
|
|
parser.)
|
|
|
|
:param subpkgname: subpackage name (e.g. "u-boot-some-device")
|
|
:returns: * full path to the aport, e.g.:
|
|
"/home/user/code/pmbootstrap/aports/main/u-boot"
|
|
* None when we couldn't find a main package
|
|
"""
|
|
# Packages ending in -dev: just assume that the originating aport has the
|
|
# same pkgname, except for the -dev at the end. If we use the other method
|
|
# below on subpackages, we may end up with the wrong package. For example,
|
|
# if something depends on plasma-framework-dev, and plasma-framework is in
|
|
# Alpine, but plasma is in pmaports, then the cutting algorithm below would
|
|
# pick plasma instead of plasma-framework.
|
|
if subpkgname.endswith("-dev"):
|
|
return guess_main_dev(args, subpkgname)
|
|
|
|
# Iterate until the cut up subpkgname is gone
|
|
words = subpkgname.split("-")
|
|
while len(words) > 1:
|
|
# Remove one dash-separated word at a time ("a-b-c" -> "a-b")
|
|
words.pop()
|
|
pkgname = "-".join(words)
|
|
|
|
# Look in pmaports
|
|
paths = glob.glob(args.aports + "/*/" + pkgname)
|
|
if paths:
|
|
logging.debug(subpkgname + ": guessed to be a subpackage of " +
|
|
pkgname)
|
|
return paths[0]
|
|
|
|
|
|
def find(args, package, must_exist=True):
|
|
"""
|
|
Find the aport path, that provides a certain subpackage.
|
|
If you want the parsed APKBUILD instead, use pmb.helpers.pmaports.get().
|
|
|
|
:param must_exist: Raise an exception, when not found
|
|
:returns: the full path to the aport folder
|
|
"""
|
|
# Try to get a cached result first (we assume, that the aports don't change
|
|
# in one pmbootstrap call)
|
|
ret = None
|
|
if package in args.cache["find_aport"]:
|
|
ret = args.cache["find_aport"][package]
|
|
else:
|
|
# Sanity check
|
|
if "*" in package:
|
|
raise RuntimeError("Invalid pkgname: " + package)
|
|
|
|
# Search in packages
|
|
paths = glob.glob(args.aports + "/*/" + package)
|
|
if len(paths) > 1:
|
|
raise RuntimeError("Package " + package + " found in multiple"
|
|
" aports subfolders. Please put it only in one"
|
|
" folder.")
|
|
elif len(paths) == 1:
|
|
ret = paths[0]
|
|
|
|
# Search in subpackages and provides
|
|
if not ret:
|
|
for path_current in glob.glob(args.aports + "/*/*/APKBUILD"):
|
|
apkbuild = pmb.parse.apkbuild(args, path_current)
|
|
found = False
|
|
|
|
# Subpackages
|
|
for subpackage_i in apkbuild["subpackages"]:
|
|
if package == subpackage_i.split(":", 1)[0]:
|
|
found = True
|
|
break
|
|
|
|
# Provides (cut off before equals sign for entries like
|
|
# "mkbootimg=0.0.1")
|
|
if not found:
|
|
for provides_i in apkbuild["provides"]:
|
|
# Ignore provides without version, they shall never be
|
|
# automatically selected
|
|
if "=" not in provides_i:
|
|
continue
|
|
|
|
if package == provides_i.split("=", 1)[0]:
|
|
found = True
|
|
break
|
|
|
|
if found:
|
|
ret = os.path.dirname(path_current)
|
|
break
|
|
|
|
# Guess a main package
|
|
if not ret:
|
|
ret = guess_main(args, package)
|
|
|
|
# Crash when necessary
|
|
if ret is None and must_exist:
|
|
raise RuntimeError("Could not find aport for package: " +
|
|
package)
|
|
|
|
# Save result in cache
|
|
args.cache["find_aport"][package] = ret
|
|
return ret
|
|
|
|
|
|
def get(args, pkgname, must_exist=True):
|
|
""" Find and parse an APKBUILD file.
|
|
Run 'pmbootstrap apkbuild_parse hello-world' for a full output example.
|
|
Relevant variables are defined in pmb.config.apkbuild_attributes.
|
|
|
|
:param pkgname: the package name to find
|
|
:param must_exist: raise an exception when it can't be found
|
|
:returns: relevant variables from the APKBUILD as dictionary, e.g.:
|
|
{ "pkgname": "hello-world",
|
|
"arch": ["all"],
|
|
"pkgrel": "4",
|
|
"pkgrel": "1",
|
|
"options": [],
|
|
... }
|
|
"""
|
|
aport = find(args, pkgname, must_exist)
|
|
if aport:
|
|
return pmb.parse.apkbuild(args, aport + "/APKBUILD")
|
|
return None
|
|
|
|
|
|
def get_repo(args, pkgname, must_exist=True):
|
|
""" Get the repository folder of an aport.
|
|
|
|
:pkgname: package name
|
|
:must_exist: raise an exception when it can't be found
|
|
:returns: a string like "main", "device", "cross", ...
|
|
or None when the aport could not be found """
|
|
aport = find(args, pkgname, must_exist)
|
|
if not aport:
|
|
return None
|
|
return os.path.basename(os.path.dirname(aport))
|
|
|
|
|
|
def check_arches(arches, arch):
|
|
""" Check if building for a certain arch is allowed.
|
|
|
|
:param arches: list of all supported arches, as it can be found in the
|
|
arch="" line of APKBUILDS (including all, noarch,
|
|
!arch, ...). For example: ["x86_64", "x86", "!armhf"]
|
|
:param arch: the architecture to check for
|
|
:returns: True when building is allowed, False otherwise
|
|
"""
|
|
if "!" + arch in arches:
|
|
return False
|
|
for value in [arch, "all", "noarch"]:
|
|
if value in arches:
|
|
return True
|
|
return False
|