OpenWRT's "./scripts/feeds install -f -p feed packagename" command forecufully
install package from selected feed if there is no installed package with same
packagename, or if there package with same packagename in core packages. If
there is package with same packagename in other feed then installation would
not happen.
In order to forecully install packages mentioned in "additional_packages"
configuration we should uninstall packages with same name firstly.
With this modification the following scenario would work:
Two feeds contains name "packagename" package
feeds/feed1/packagename/Makefile
feeds/feed2/packagename/Makefile
Profiles installs both this feeds and selects exact feed that should
provide the package:
feeds:
- name: feed1
- name: feed2
additional_packages:
- feed: feed2
packages:
- packagename
References: PCF-1866
Signed-off-by: Nikolai Merinov <n.merinov@inango-systems.com>
249 lines
7.7 KiB
Python
Executable File
249 lines
7.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import yaml
|
|
from pathlib import Path
|
|
from shutil import rmtree
|
|
import io
|
|
import re
|
|
import sys
|
|
from subprocess import run
|
|
from os import getenv
|
|
|
|
sys.stdout = io.TextIOWrapper(open(sys.stdout.fileno(), "wb", 0), write_through=True)
|
|
profile_folders = getenv("GENCONFIG_PROFILE_DIRS", "./profiles")
|
|
|
|
|
|
def run_cmd(cmd: list):
|
|
if getenv("GENCONFIG_VERBOSE"):
|
|
print('run_cmd: "' + " ".join(cmd) + '"')
|
|
return run(cmd)
|
|
|
|
|
|
def die(*messages: str) -> None:
|
|
"""Prints each message on a new line and exits the program."""
|
|
for message in messages:
|
|
print(message)
|
|
quit(1)
|
|
|
|
|
|
def usage(code: int = 0):
|
|
"""Print script usage
|
|
|
|
Args:
|
|
code (int): exit code
|
|
"""
|
|
print(
|
|
f"""Usage: {sys.argv[0]} <profile> [options...]
|
|
|
|
clean Cleanup feeds related parts in the tree and exit.
|
|
list List available profiles"""
|
|
)
|
|
quit(code)
|
|
|
|
|
|
def load_yaml(fname: str, profile: dict):
|
|
# Allow profile overriding by searching in reverse order
|
|
for folder in profile_folders.split(':')[::-1]:
|
|
profile_file = (Path(folder) / fname).with_suffix(".yml")
|
|
|
|
if not profile_file.is_file():
|
|
continue
|
|
|
|
new = yaml.safe_load(profile_file.read_text())
|
|
for n in new:
|
|
if n in {"target", "subtarget", "external_target"}:
|
|
if profile.get(n):
|
|
die(f"Duplicate tag found {n}")
|
|
profile.update({n: new.get(n)})
|
|
elif n in {"description"}:
|
|
profile["description"].append(new.get(n))
|
|
elif n in {"packages"}:
|
|
profile["packages"].extend(new.get(n))
|
|
elif n in {"profiles"}:
|
|
profile["profiles"].extend(new.get(n))
|
|
elif n in {"diffconfig"}:
|
|
profile["diffconfig"] += new.get(n)
|
|
elif n in {"feeds"}:
|
|
for f in new.get(n):
|
|
if f.get("name", "") == "" or f.get("uri", "") == "":
|
|
die(f"Found bad feed {f}")
|
|
profile["feeds"][f.get("name")] = f
|
|
elif n in {"additional_packages"}:
|
|
for f in new.get(n):
|
|
if not f.get("feed") or not f.get("packages"):
|
|
die(f"Found bad additional_packages {f}")
|
|
profile["additional_packages"].extend(new.get(n))
|
|
return profile
|
|
|
|
if not profile_file.is_file():
|
|
die(f"Profile {fname} not found")
|
|
|
|
|
|
def extract_sha1_from_revision(revision: str) -> str:
|
|
"""
|
|
Validates the given revision string and extracts the SHA-1 hash.
|
|
|
|
A valid revision can be:
|
|
|
|
1. A full 40-character Git SHA-1 hash.
|
|
2. A human readable reference like Git tag followed by '@' and a full 40-character Git SHA-1 hash.
|
|
|
|
:param revision: The revision string to validate and extract the SHA-1 hash from.
|
|
:return: The extracted SHA-1 hash if valid, otherwise None.
|
|
"""
|
|
|
|
full_sha1_pattern = r"^[a-f0-9]{40}$"
|
|
tag_full_sha1_pattern = r"^[^@]+@([a-f0-9]{40})$"
|
|
|
|
if re.match(full_sha1_pattern, revision):
|
|
return revision
|
|
elif re.match(tag_full_sha1_pattern, revision):
|
|
return re.match(tag_full_sha1_pattern, revision).group(1)
|
|
else:
|
|
return None
|
|
|
|
|
|
def handle_feed_revision(profile_feed: dict, feeds: list):
|
|
method = profile_feed.get("method", "src-git")
|
|
f = f'{method},{profile_feed["name"]},{profile_feed["uri"]}'
|
|
|
|
if method.startswith('src-git'):
|
|
revision = profile_feed.get("revision")
|
|
if not revision:
|
|
die(f"Please specify `revision` for the following feed: {profile_feed}")
|
|
|
|
sha1 = extract_sha1_from_revision(revision)
|
|
if not sha1:
|
|
die(
|
|
f"Invalid feed revision {revision} in {profile_feed} feed, valid `revision` is:",
|
|
" 1. A full 40-character Git SHA-1 hash.",
|
|
" 2. A human readable reference like Git tag followed by '@' and a full 40-character Git SHA-1 hash.",
|
|
)
|
|
f += f'^{sha1}'
|
|
|
|
feeds.append(f)
|
|
|
|
|
|
if "list" in sys.argv:
|
|
for folder in profile_folders.split(':')[::-1]:
|
|
print(f"Profiles in {folder}")
|
|
|
|
print("\n".join(map(lambda p: str(p.stem), Path(folder).glob("*.yml"))))
|
|
quit(0)
|
|
|
|
if "help" in sys.argv:
|
|
usage()
|
|
|
|
if len(sys.argv) < 2:
|
|
usage(1)
|
|
|
|
rmtree("./tmp", ignore_errors=True)
|
|
rmtree("./package/feeds/", ignore_errors=True)
|
|
rmtree("./feeds", ignore_errors=True)
|
|
rmtree("./tmp", ignore_errors=True)
|
|
if Path("./feeds.conf").is_file():
|
|
Path("./feeds.conf").unlink()
|
|
if Path("./.config").is_file():
|
|
Path("./.config").unlink()
|
|
|
|
if "clean" in sys.argv:
|
|
print("Tree is now clean")
|
|
quit(0)
|
|
|
|
profile = {
|
|
"additional_packages": [],
|
|
"description": [],
|
|
"diffconfig": "",
|
|
"feeds": {},
|
|
"packages": [],
|
|
"profiles": [],
|
|
}
|
|
|
|
for p in sys.argv[1:]:
|
|
profile = load_yaml(p, profile)
|
|
|
|
if getenv("GENCONFIG_VERBOSE"):
|
|
print(yaml.dump(profile))
|
|
|
|
for d in profile.get("description"):
|
|
print(d)
|
|
|
|
feeds_conf = Path("feeds.conf")
|
|
if feeds_conf.is_file():
|
|
feeds_conf.unlink()
|
|
|
|
feeds = []
|
|
|
|
for p in profile.get("feeds", []):
|
|
try:
|
|
profile_feeds = profile["feeds"].get(p)
|
|
handle_feed_revision(profile_feeds, feeds)
|
|
except:
|
|
print(f"Badly configured feed: {profile_feeds}")
|
|
quit(1)
|
|
|
|
with open("feeds.conf.default", "r") as default_feeds:
|
|
for line in default_feeds:
|
|
feed = line.rstrip()
|
|
print(f"Adding default feed '{feed}'")
|
|
feeds.append(feed.replace(" ", ","))
|
|
|
|
if run_cmd(["./scripts/feeds", "setup", *feeds]).returncode:
|
|
die(f"Error setting up feeds")
|
|
|
|
if run_cmd(["./scripts/feeds", "update"]).returncode:
|
|
die(f"Error updating feeds")
|
|
|
|
for p in profile.get("feeds", []):
|
|
f = profile["feeds"].get(p)
|
|
if run_cmd(
|
|
["./scripts/feeds", "install", "-a", "-f", "-p", f.get("name")]
|
|
).returncode:
|
|
die(f"Error installing {feed}")
|
|
|
|
for ap in profile.get("additional_packages"):
|
|
feed = ap["feed"]
|
|
# "install -f -p feed package" will override only base package and would not override package from other feed without uninstall
|
|
run_cmd(["./scripts/feeds", "uninstall", *ap["packages"]])
|
|
if run_cmd(
|
|
["./scripts/feeds", "install", "-f", "-p", feed, *ap["packages"]]
|
|
).returncode:
|
|
packages_install = " ".join(ap["packages"])
|
|
die(f"Error installing additional packages {packages_install} from {feed} feed")
|
|
|
|
if profile.get("external_target", False):
|
|
if run_cmd(["./scripts/feeds", "install", "-f", profile["target"]]).returncode:
|
|
die(f"Error installing external target {profile['target']}")
|
|
|
|
config_output = f"""CONFIG_TARGET_{profile["target"]}=y
|
|
CONFIG_TARGET_{profile["target"]}_{profile["subtarget"]}=y\n"""
|
|
profiles = profile.get("profiles")
|
|
if len(profiles) > 1:
|
|
config_output += f"CONFIG_TARGET_MULTI_PROFILE=y\n"
|
|
for p in profiles:
|
|
config_output += f"""CONFIG_TARGET_DEVICE_{profile["target"]}_{profile["subtarget"]}_DEVICE_{p}=y\n"""
|
|
else:
|
|
config_output += f"""CONFIG_TARGET_{profile["target"]}_{profile["subtarget"]}_DEVICE_{profiles[0]}=y\n"""
|
|
|
|
for package in profile.get("packages", []):
|
|
print(f"Add package to .config: {package}")
|
|
config_output += f"CONFIG_PACKAGE_{package}=y\n"
|
|
|
|
for ap in profile.get("additional_packages"):
|
|
for package in ap["packages"]:
|
|
print(f"Add additional package to .config: {package}")
|
|
config_output += f"CONFIG_PACKAGE_{package}=y\n"
|
|
|
|
config_output += f"{profile.get('diffconfig', '')}"
|
|
|
|
Path(".config").write_text(config_output)
|
|
print("Configuration written to .config")
|
|
|
|
if getenv("GENCONFIG_VERBOSE"):
|
|
print(config_output)
|
|
|
|
rmtree("./tmp", ignore_errors=True)
|
|
print("Running make defconfig")
|
|
if run_cmd(["make", "defconfig"]).returncode:
|
|
die(f"Error running make defconfig")
|