1070 lines
33 KiB
Python
1070 lines
33 KiB
Python
|
# ex:ts=4:sw=4:sts=4:et
|
||
|
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
|
||
|
#
|
||
|
# Copyright (c) 2012, Intel Corporation.
|
||
|
# All rights reserved.
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License version 2 as
|
||
|
# published by the Free Software Foundation.
|
||
|
#
|
||
|
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||
|
#
|
||
|
# DESCRIPTION
|
||
|
# This module implements the kernel-related functions used by
|
||
|
# 'yocto-kernel' to manage kernel config items and patches for Yocto
|
||
|
# BSPs.
|
||
|
#
|
||
|
# AUTHORS
|
||
|
# Tom Zanussi <tom.zanussi (at] intel.com>
|
||
|
#
|
||
|
|
||
|
import sys
|
||
|
import os
|
||
|
import shutil
|
||
|
from .tags import *
|
||
|
import glob
|
||
|
import subprocess
|
||
|
from .engine import create_context
|
||
|
|
||
|
def find_bblayers():
|
||
|
"""
|
||
|
Find and return a sanitized list of the layers found in BBLAYERS.
|
||
|
"""
|
||
|
try:
|
||
|
builddir = os.environ["BUILDDIR"]
|
||
|
except KeyError:
|
||
|
print("BUILDDIR not found, exiting. (Did you forget to source oe-init-build-env?)")
|
||
|
sys.exit(1)
|
||
|
bblayers_conf = os.path.join(builddir, "conf/bblayers.conf")
|
||
|
|
||
|
layers = []
|
||
|
|
||
|
bitbake_env_cmd = "bitbake -e"
|
||
|
bitbake_env_lines = subprocess.Popen(bitbake_env_cmd, shell=True,
|
||
|
stdout=subprocess.PIPE).stdout.read().decode('utf-8')
|
||
|
|
||
|
if not bitbake_env_lines:
|
||
|
print("Couldn't get '%s' output, exiting." % bitbake_env_cmd)
|
||
|
sys.exit(1)
|
||
|
|
||
|
for line in bitbake_env_lines.split('\n'):
|
||
|
bblayers = get_line_val(line, "BBLAYERS")
|
||
|
if (bblayers):
|
||
|
break
|
||
|
|
||
|
if not bblayers:
|
||
|
print("Couldn't find BBLAYERS in %s output, exiting." % bitbake_env_cmd)
|
||
|
sys.exit(1)
|
||
|
|
||
|
raw_layers = bblayers.split()
|
||
|
|
||
|
for layer in raw_layers:
|
||
|
if layer == 'BBLAYERS' or '=' in layer:
|
||
|
continue
|
||
|
layers.append(layer)
|
||
|
|
||
|
return layers
|
||
|
|
||
|
|
||
|
def get_line_val(line, key):
|
||
|
"""
|
||
|
Extract the value from the VAR="val" string
|
||
|
"""
|
||
|
if line.startswith(key + "="):
|
||
|
stripped_line = line.split('=')[1]
|
||
|
stripped_line = stripped_line.replace('\"', '')
|
||
|
return stripped_line
|
||
|
return None
|
||
|
|
||
|
|
||
|
def find_meta_layer():
|
||
|
"""
|
||
|
Find and return the meta layer in BBLAYERS.
|
||
|
"""
|
||
|
layers = find_bblayers()
|
||
|
|
||
|
for layer in layers:
|
||
|
if layer.endswith("meta"):
|
||
|
return layer
|
||
|
|
||
|
return None
|
||
|
|
||
|
|
||
|
def find_bsp_layer(machine):
|
||
|
"""
|
||
|
Find and return a machine's BSP layer in BBLAYERS.
|
||
|
"""
|
||
|
layers = find_bblayers()
|
||
|
|
||
|
for layer in layers:
|
||
|
if layer.endswith(machine):
|
||
|
return layer
|
||
|
|
||
|
print("Unable to find the BSP layer for machine %s." % machine)
|
||
|
print("Please make sure it is listed in bblayers.conf")
|
||
|
sys.exit(1)
|
||
|
|
||
|
|
||
|
def gen_choices_str(choices):
|
||
|
"""
|
||
|
Generate a numbered list of choices from a list of choices for
|
||
|
display to the user.
|
||
|
"""
|
||
|
choices_str = ""
|
||
|
|
||
|
for i, choice in enumerate(choices):
|
||
|
choices_str += "\t" + str(i + 1) + ") " + choice + "\n"
|
||
|
|
||
|
return choices_str
|
||
|
|
||
|
|
||
|
def open_user_file(scripts_path, machine, userfile, mode):
|
||
|
"""
|
||
|
Find one of the user files (user-config.cfg, user-patches.scc)
|
||
|
associated with the machine (could be in files/,
|
||
|
linux-yocto-custom/, etc). Returns the open file if found, None
|
||
|
otherwise.
|
||
|
|
||
|
The caller is responsible for closing the file returned.
|
||
|
"""
|
||
|
layer = find_bsp_layer(machine)
|
||
|
linuxdir = os.path.join(layer, "recipes-kernel/linux")
|
||
|
linuxdir_list = os.listdir(linuxdir)
|
||
|
for fileobj in linuxdir_list:
|
||
|
fileobj_path = os.path.join(linuxdir, fileobj)
|
||
|
if os.path.isdir(fileobj_path):
|
||
|
userfile_name = os.path.join(fileobj_path, userfile)
|
||
|
try:
|
||
|
f = open(userfile_name, mode)
|
||
|
return f
|
||
|
except IOError:
|
||
|
continue
|
||
|
return None
|
||
|
|
||
|
|
||
|
def read_config_items(scripts_path, machine):
|
||
|
"""
|
||
|
Find and return a list of config items (CONFIG_XXX) in a machine's
|
||
|
user-defined config fragment [${machine}-user-config.cfg].
|
||
|
"""
|
||
|
config_items = []
|
||
|
|
||
|
f = open_user_file(scripts_path, machine, machine+"-user-config.cfg", "r")
|
||
|
lines = f.readlines()
|
||
|
for line in lines:
|
||
|
s = line.strip()
|
||
|
if s and not s.startswith("#"):
|
||
|
config_items.append(s)
|
||
|
f.close()
|
||
|
|
||
|
return config_items
|
||
|
|
||
|
|
||
|
def write_config_items(scripts_path, machine, config_items):
|
||
|
"""
|
||
|
Write (replace) the list of config items (CONFIG_XXX) in a
|
||
|
machine's user-defined config fragment [${machine}=user-config.cfg].
|
||
|
"""
|
||
|
f = open_user_file(scripts_path, machine, machine+"-user-config.cfg", "w")
|
||
|
for item in config_items:
|
||
|
f.write(item + "\n")
|
||
|
f.close()
|
||
|
|
||
|
kernel_contents_changed(scripts_path, machine)
|
||
|
|
||
|
|
||
|
def yocto_kernel_config_list(scripts_path, machine):
|
||
|
"""
|
||
|
Display the list of config items (CONFIG_XXX) in a machine's
|
||
|
user-defined config fragment [${machine}-user-config.cfg].
|
||
|
"""
|
||
|
config_items = read_config_items(scripts_path, machine)
|
||
|
|
||
|
print("The current set of machine-specific kernel config items for %s is:" % machine)
|
||
|
print(gen_choices_str(config_items))
|
||
|
|
||
|
|
||
|
def yocto_kernel_config_rm(scripts_path, machine):
|
||
|
"""
|
||
|
Display the list of config items (CONFIG_XXX) in a machine's
|
||
|
user-defined config fragment [${machine}-user-config.cfg], prompt the user
|
||
|
for one or more to remove, and remove them.
|
||
|
"""
|
||
|
config_items = read_config_items(scripts_path, machine)
|
||
|
|
||
|
print("Specify the kernel config items to remove:")
|
||
|
inp = input(gen_choices_str(config_items))
|
||
|
rm_choices = inp.split()
|
||
|
rm_choices.sort()
|
||
|
|
||
|
removed = []
|
||
|
|
||
|
for choice in reversed(rm_choices):
|
||
|
try:
|
||
|
idx = int(choice) - 1
|
||
|
except ValueError:
|
||
|
print("Invalid choice (%s), exiting" % choice)
|
||
|
sys.exit(1)
|
||
|
if idx < 0 or idx >= len(config_items):
|
||
|
print("Invalid choice (%d), exiting" % (idx + 1))
|
||
|
sys.exit(1)
|
||
|
removed.append(config_items.pop(idx))
|
||
|
|
||
|
write_config_items(scripts_path, machine, config_items)
|
||
|
|
||
|
print("Removed items:")
|
||
|
for r in removed:
|
||
|
print("\t%s" % r)
|
||
|
|
||
|
|
||
|
def yocto_kernel_config_add(scripts_path, machine, config_items):
|
||
|
"""
|
||
|
Add one or more config items (CONFIG_XXX) to a machine's
|
||
|
user-defined config fragment [${machine}-user-config.cfg].
|
||
|
"""
|
||
|
new_items = []
|
||
|
dup_items = []
|
||
|
|
||
|
cur_items = read_config_items(scripts_path, machine)
|
||
|
|
||
|
for item in config_items:
|
||
|
if not item.startswith("CONFIG") or (not "=y" in item and not "=m" in item):
|
||
|
print("Invalid config item (%s), exiting" % item)
|
||
|
sys.exit(1)
|
||
|
if item not in cur_items and item not in new_items:
|
||
|
new_items.append(item)
|
||
|
else:
|
||
|
dup_items.append(item)
|
||
|
|
||
|
if len(new_items) > 0:
|
||
|
cur_items.extend(new_items)
|
||
|
write_config_items(scripts_path, machine, cur_items)
|
||
|
print("Added item%s:" % ("" if len(new_items)==1 else "s"))
|
||
|
for n in new_items:
|
||
|
print("\t%s" % n)
|
||
|
|
||
|
if len(dup_items) > 0:
|
||
|
output="The following item%s already exist%s in the current configuration, ignoring %s:" % \
|
||
|
(("","s", "it") if len(dup_items)==1 else ("s", "", "them" ))
|
||
|
print(output)
|
||
|
for n in dup_items:
|
||
|
print("\t%s" % n)
|
||
|
|
||
|
def find_current_kernel(bsp_layer, machine):
|
||
|
"""
|
||
|
Determine the kernel and version currently being used in the BSP.
|
||
|
"""
|
||
|
machine_conf = os.path.join(bsp_layer, "conf/machine/" + machine + ".conf")
|
||
|
|
||
|
preferred_kernel = preferred_kernel_version = preferred_version_varname = None
|
||
|
|
||
|
f = open(machine_conf, "r")
|
||
|
lines = f.readlines()
|
||
|
for line in lines:
|
||
|
if line.strip().startswith("PREFERRED_PROVIDER_virtual/kernel"):
|
||
|
preferred_kernel = line.split()[-1]
|
||
|
preferred_kernel = preferred_kernel.replace('\"','')
|
||
|
preferred_version_varname = "PREFERRED_VERSION_" + preferred_kernel
|
||
|
if preferred_version_varname and line.strip().startswith(preferred_version_varname):
|
||
|
preferred_kernel_version = line.split()[-1]
|
||
|
preferred_kernel_version = preferred_kernel_version.replace('\"','')
|
||
|
preferred_kernel_version = preferred_kernel_version.replace('%','')
|
||
|
|
||
|
if preferred_kernel and preferred_kernel_version:
|
||
|
return preferred_kernel + "_" + preferred_kernel_version
|
||
|
elif preferred_kernel:
|
||
|
return preferred_kernel
|
||
|
|
||
|
|
||
|
def find_filesdir(scripts_path, machine):
|
||
|
"""
|
||
|
Find the name of the 'files' dir associated with the machine
|
||
|
(could be in files/, linux-yocto-custom/, etc). Returns the name
|
||
|
of the files dir if found, None otherwise.
|
||
|
"""
|
||
|
layer = find_bsp_layer(machine)
|
||
|
filesdir = None
|
||
|
linuxdir = os.path.join(layer, "recipes-kernel/linux")
|
||
|
linuxdir_list = os.listdir(linuxdir)
|
||
|
for fileobj in linuxdir_list:
|
||
|
fileobj_path = os.path.join(linuxdir, fileobj)
|
||
|
if os.path.isdir(fileobj_path):
|
||
|
# this could be files/ or linux-yocto-custom/, we have no way of distinguishing
|
||
|
# so we take the first (and normally only) dir we find as the 'filesdir'
|
||
|
filesdir = fileobj_path
|
||
|
|
||
|
return filesdir
|
||
|
|
||
|
|
||
|
def read_patch_items(scripts_path, machine):
|
||
|
"""
|
||
|
Find and return a list of patch items in a machine's user-defined
|
||
|
patch list [${machine}-user-patches.scc].
|
||
|
"""
|
||
|
patch_items = []
|
||
|
|
||
|
f = open_user_file(scripts_path, machine, machine+"-user-patches.scc", "r")
|
||
|
lines = f.readlines()
|
||
|
for line in lines:
|
||
|
s = line.strip()
|
||
|
if s and not s.startswith("#"):
|
||
|
fields = s.split()
|
||
|
if not fields[0] == "patch":
|
||
|
continue
|
||
|
patch_items.append(fields[1])
|
||
|
f.close()
|
||
|
|
||
|
return patch_items
|
||
|
|
||
|
|
||
|
def write_patch_items(scripts_path, machine, patch_items):
|
||
|
"""
|
||
|
Write (replace) the list of patches in a machine's user-defined
|
||
|
patch list [${machine}-user-patches.scc].
|
||
|
"""
|
||
|
f = open_user_file(scripts_path, machine, machine+"-user-patches.scc", "w")
|
||
|
for item in patch_items:
|
||
|
f.write("patch " + item + "\n")
|
||
|
f.close()
|
||
|
|
||
|
kernel_contents_changed(scripts_path, machine)
|
||
|
|
||
|
|
||
|
def yocto_kernel_patch_list(scripts_path, machine):
|
||
|
"""
|
||
|
Display the list of patches in a machine's user-defined patch list
|
||
|
[${machine}-user-patches.scc].
|
||
|
"""
|
||
|
patches = read_patch_items(scripts_path, machine)
|
||
|
|
||
|
print("The current set of machine-specific patches for %s is:" % machine)
|
||
|
print(gen_choices_str(patches))
|
||
|
|
||
|
|
||
|
def yocto_kernel_patch_rm(scripts_path, machine):
|
||
|
"""
|
||
|
Remove one or more patches from a machine's user-defined patch
|
||
|
list [${machine}-user-patches.scc].
|
||
|
"""
|
||
|
patches = read_patch_items(scripts_path, machine)
|
||
|
|
||
|
print("Specify the patches to remove:")
|
||
|
inp = input(gen_choices_str(patches))
|
||
|
rm_choices = inp.split()
|
||
|
rm_choices.sort()
|
||
|
|
||
|
removed = []
|
||
|
|
||
|
filesdir = find_filesdir(scripts_path, machine)
|
||
|
if not filesdir:
|
||
|
print("Couldn't rm patch(es) since we couldn't find a 'files' dir")
|
||
|
sys.exit(1)
|
||
|
|
||
|
for choice in reversed(rm_choices):
|
||
|
try:
|
||
|
idx = int(choice) - 1
|
||
|
except ValueError:
|
||
|
print("Invalid choice (%s), exiting" % choice)
|
||
|
sys.exit(1)
|
||
|
if idx < 0 or idx >= len(patches):
|
||
|
print("Invalid choice (%d), exiting" % (idx + 1))
|
||
|
sys.exit(1)
|
||
|
filesdir_patch = os.path.join(filesdir, patches[idx])
|
||
|
if os.path.isfile(filesdir_patch):
|
||
|
os.remove(filesdir_patch)
|
||
|
removed.append(patches[idx])
|
||
|
patches.pop(idx)
|
||
|
|
||
|
write_patch_items(scripts_path, machine, patches)
|
||
|
|
||
|
print("Removed patches:")
|
||
|
for r in removed:
|
||
|
print("\t%s" % r)
|
||
|
|
||
|
|
||
|
def yocto_kernel_patch_add(scripts_path, machine, patches):
|
||
|
"""
|
||
|
Add one or more patches to a machine's user-defined patch list
|
||
|
[${machine}-user-patches.scc].
|
||
|
"""
|
||
|
existing_patches = read_patch_items(scripts_path, machine)
|
||
|
|
||
|
for patch in patches:
|
||
|
if os.path.basename(patch) in existing_patches:
|
||
|
print("Couldn't add patch (%s) since it's already been added" % os.path.basename(patch))
|
||
|
sys.exit(1)
|
||
|
|
||
|
filesdir = find_filesdir(scripts_path, machine)
|
||
|
if not filesdir:
|
||
|
print("Couldn't add patch (%s) since we couldn't find a 'files' dir to add it to" % os.path.basename(patch))
|
||
|
sys.exit(1)
|
||
|
|
||
|
new_patches = []
|
||
|
|
||
|
for patch in patches:
|
||
|
if not os.path.isfile(patch):
|
||
|
print("Couldn't find patch (%s), exiting" % patch)
|
||
|
sys.exit(1)
|
||
|
basename = os.path.basename(patch)
|
||
|
filesdir_patch = os.path.join(filesdir, basename)
|
||
|
shutil.copyfile(patch, filesdir_patch)
|
||
|
new_patches.append(basename)
|
||
|
|
||
|
cur_items = read_patch_items(scripts_path, machine)
|
||
|
cur_items.extend(new_patches)
|
||
|
write_patch_items(scripts_path, machine, cur_items)
|
||
|
|
||
|
print("Added patches:")
|
||
|
for n in new_patches:
|
||
|
print("\t%s" % n)
|
||
|
|
||
|
|
||
|
def inc_pr(line):
|
||
|
"""
|
||
|
Add 1 to the PR value in the given bbappend PR line. For the PR
|
||
|
lines in kernel .bbappends after modifications. Handles PRs of
|
||
|
the form PR := "${PR}.1" as well as PR = "r0".
|
||
|
"""
|
||
|
idx = line.find("\"")
|
||
|
|
||
|
pr_str = line[idx:]
|
||
|
pr_str = pr_str.replace('\"','')
|
||
|
fields = pr_str.split('.')
|
||
|
if len(fields) > 1:
|
||
|
fields[1] = str(int(fields[1]) + 1)
|
||
|
pr_str = "\"" + '.'.join(fields) + "\"\n"
|
||
|
else:
|
||
|
pr_val = pr_str[1:]
|
||
|
pr_str = "\"" + "r" + str(int(pr_val) + 1) + "\"\n"
|
||
|
idx2 = line.find("\"", idx + 1)
|
||
|
line = line[:idx] + pr_str
|
||
|
|
||
|
return line
|
||
|
|
||
|
|
||
|
def kernel_contents_changed(scripts_path, machine):
|
||
|
"""
|
||
|
Do what we need to do to notify the system that the kernel
|
||
|
recipe's contents have changed.
|
||
|
"""
|
||
|
layer = find_bsp_layer(machine)
|
||
|
|
||
|
kernel = find_current_kernel(layer, machine)
|
||
|
if not kernel:
|
||
|
print("Couldn't determine the kernel for this BSP, exiting.")
|
||
|
sys.exit(1)
|
||
|
|
||
|
kernel_bbfile = os.path.join(layer, "recipes-kernel/linux/" + kernel + ".bbappend")
|
||
|
if not os.path.isfile(kernel_bbfile):
|
||
|
kernel_bbfile = os.path.join(layer, "recipes-kernel/linux/" + kernel + ".bb")
|
||
|
if not os.path.isfile(kernel_bbfile):
|
||
|
return
|
||
|
kernel_bbfile_prev = kernel_bbfile + ".prev"
|
||
|
shutil.copyfile(kernel_bbfile, kernel_bbfile_prev)
|
||
|
|
||
|
ifile = open(kernel_bbfile_prev, "r")
|
||
|
ofile = open(kernel_bbfile, "w")
|
||
|
ifile_lines = ifile.readlines()
|
||
|
for ifile_line in ifile_lines:
|
||
|
if ifile_line.strip().startswith("PR"):
|
||
|
ifile_line = inc_pr(ifile_line)
|
||
|
ofile.write(ifile_line)
|
||
|
ofile.close()
|
||
|
ifile.close()
|
||
|
|
||
|
|
||
|
def kernels(context):
|
||
|
"""
|
||
|
Return the list of available kernels in the BSP i.e. corresponding
|
||
|
to the kernel .bbappends found in the layer.
|
||
|
"""
|
||
|
archdir = os.path.join(context["scripts_path"], "lib/bsp/substrate/target/arch/" + context["arch"])
|
||
|
kerndir = os.path.join(archdir, "recipes-kernel/linux")
|
||
|
bbglob = os.path.join(kerndir, "*.bbappend")
|
||
|
|
||
|
bbappends = glob.glob(bbglob)
|
||
|
|
||
|
kernels = []
|
||
|
|
||
|
for kernel in bbappends:
|
||
|
filename = os.path.splitext(os.path.basename(kernel))[0]
|
||
|
idx = filename.find(CLOSE_TAG)
|
||
|
if idx != -1:
|
||
|
filename = filename[idx + len(CLOSE_TAG):].strip()
|
||
|
kernels.append(filename)
|
||
|
|
||
|
kernels.append("custom")
|
||
|
|
||
|
return kernels
|
||
|
|
||
|
|
||
|
def extract_giturl(file):
|
||
|
"""
|
||
|
Extract the git url of the kernel repo from the kernel recipe's
|
||
|
SRC_URI.
|
||
|
"""
|
||
|
url = None
|
||
|
f = open(file, "r")
|
||
|
lines = f.readlines()
|
||
|
for line in lines:
|
||
|
line = line.strip()
|
||
|
if line.startswith("SRC_URI"):
|
||
|
line = line[len("SRC_URI"):].strip()
|
||
|
if line.startswith("="):
|
||
|
line = line[1:].strip()
|
||
|
if line.startswith("\""):
|
||
|
line = line[1:].strip()
|
||
|
prot = "git"
|
||
|
for s in line.split(";"):
|
||
|
if s.startswith("git://"):
|
||
|
url = s
|
||
|
if s.startswith("protocol="):
|
||
|
prot = s.split("=")[1]
|
||
|
if url:
|
||
|
url = prot + url[3:]
|
||
|
return url
|
||
|
|
||
|
|
||
|
def find_giturl(context):
|
||
|
"""
|
||
|
Find the git url of the kernel repo from the kernel recipe's
|
||
|
SRC_URI.
|
||
|
"""
|
||
|
name = context["name"]
|
||
|
filebase = context["filename"]
|
||
|
scripts_path = context["scripts_path"]
|
||
|
|
||
|
meta_layer = find_meta_layer()
|
||
|
|
||
|
kerndir = os.path.join(meta_layer, "recipes-kernel/linux")
|
||
|
bbglob = os.path.join(kerndir, "*.bb")
|
||
|
bbs = glob.glob(bbglob)
|
||
|
for kernel in bbs:
|
||
|
filename = os.path.splitext(os.path.basename(kernel))[0]
|
||
|
if filename in filebase:
|
||
|
giturl = extract_giturl(kernel)
|
||
|
return giturl
|
||
|
|
||
|
return None
|
||
|
|
||
|
|
||
|
def read_features(scripts_path, machine):
|
||
|
"""
|
||
|
Find and return a list of features in a machine's user-defined
|
||
|
features fragment [${machine}-user-features.scc].
|
||
|
"""
|
||
|
features = []
|
||
|
|
||
|
f = open_user_file(scripts_path, machine, machine+"-user-features.scc", "r")
|
||
|
lines = f.readlines()
|
||
|
for line in lines:
|
||
|
s = line.strip()
|
||
|
if s and not s.startswith("#"):
|
||
|
feature_include = s.split()
|
||
|
features.append(feature_include[1].strip())
|
||
|
f.close()
|
||
|
|
||
|
return features
|
||
|
|
||
|
|
||
|
def write_features(scripts_path, machine, features):
|
||
|
"""
|
||
|
Write (replace) the list of feature items in a
|
||
|
machine's user-defined features fragment [${machine}=user-features.cfg].
|
||
|
"""
|
||
|
f = open_user_file(scripts_path, machine, machine+"-user-features.scc", "w")
|
||
|
for item in features:
|
||
|
f.write("include " + item + "\n")
|
||
|
f.close()
|
||
|
|
||
|
kernel_contents_changed(scripts_path, machine)
|
||
|
|
||
|
|
||
|
def yocto_kernel_feature_list(scripts_path, machine):
|
||
|
"""
|
||
|
Display the list of features used in a machine's user-defined
|
||
|
features fragment [${machine}-user-features.scc].
|
||
|
"""
|
||
|
features = read_features(scripts_path, machine)
|
||
|
|
||
|
print("The current set of machine-specific features for %s is:" % machine)
|
||
|
print(gen_choices_str(features))
|
||
|
|
||
|
|
||
|
def yocto_kernel_feature_rm(scripts_path, machine):
|
||
|
"""
|
||
|
Display the list of features used in a machine's user-defined
|
||
|
features fragment [${machine}-user-features.scc], prompt the user
|
||
|
for one or more to remove, and remove them.
|
||
|
"""
|
||
|
features = read_features(scripts_path, machine)
|
||
|
|
||
|
print("Specify the features to remove:")
|
||
|
inp = input(gen_choices_str(features))
|
||
|
rm_choices = inp.split()
|
||
|
rm_choices.sort()
|
||
|
|
||
|
removed = []
|
||
|
|
||
|
for choice in reversed(rm_choices):
|
||
|
try:
|
||
|
idx = int(choice) - 1
|
||
|
except ValueError:
|
||
|
print("Invalid choice (%s), exiting" % choice)
|
||
|
sys.exit(1)
|
||
|
if idx < 0 or idx >= len(features):
|
||
|
print("Invalid choice (%d), exiting" % (idx + 1))
|
||
|
sys.exit(1)
|
||
|
removed.append(features.pop(idx))
|
||
|
|
||
|
write_features(scripts_path, machine, features)
|
||
|
|
||
|
print("Removed features:")
|
||
|
for r in removed:
|
||
|
print("\t%s" % r)
|
||
|
|
||
|
|
||
|
def yocto_kernel_feature_add(scripts_path, machine, features):
|
||
|
"""
|
||
|
Add one or more features a machine's user-defined features
|
||
|
fragment [${machine}-user-features.scc].
|
||
|
"""
|
||
|
new_items = []
|
||
|
|
||
|
for item in features:
|
||
|
if not item.endswith(".scc"):
|
||
|
print("Invalid feature (%s), exiting" % item)
|
||
|
sys.exit(1)
|
||
|
new_items.append(item)
|
||
|
|
||
|
cur_items = read_features(scripts_path, machine)
|
||
|
cur_items.extend(new_items)
|
||
|
|
||
|
write_features(scripts_path, machine, cur_items)
|
||
|
|
||
|
print("Added features:")
|
||
|
for n in new_items:
|
||
|
print("\t%s" % n)
|
||
|
|
||
|
|
||
|
def find_feature_url(git_url):
|
||
|
"""
|
||
|
Find the url of the kern-features.rc kernel for the kernel repo
|
||
|
specified from the BSP's kernel recipe SRC_URI.
|
||
|
"""
|
||
|
feature_url = ""
|
||
|
if git_url.startswith("git://"):
|
||
|
git_url = git_url[len("git://"):].strip()
|
||
|
s = git_url.split("/")
|
||
|
if s[1].endswith(".git"):
|
||
|
s[1] = s[1][:len(s[1]) - len(".git")]
|
||
|
feature_url = "http://" + s[0] + "/cgit/cgit.cgi/" + s[1] + \
|
||
|
"/plain/meta/cfg/kern-features.rc?h=meta"
|
||
|
|
||
|
return feature_url
|
||
|
|
||
|
|
||
|
def find_feature_desc(lines):
|
||
|
"""
|
||
|
Find the feature description and compatibility in the passed-in
|
||
|
set of lines. Returns a string string of the form 'desc
|
||
|
[compat]'.
|
||
|
"""
|
||
|
desc = "no description available"
|
||
|
compat = "unknown"
|
||
|
|
||
|
for line in lines:
|
||
|
idx = line.find("KFEATURE_DESCRIPTION")
|
||
|
if idx != -1:
|
||
|
desc = line[idx + len("KFEATURE_DESCRIPTION"):].strip()
|
||
|
if desc.startswith("\""):
|
||
|
desc = desc[1:]
|
||
|
if desc.endswith("\""):
|
||
|
desc = desc[:-1]
|
||
|
else:
|
||
|
idx = line.find("KFEATURE_COMPATIBILITY")
|
||
|
if idx != -1:
|
||
|
compat = line[idx + len("KFEATURE_COMPATIBILITY"):].strip()
|
||
|
|
||
|
return desc + " [" + compat + "]"
|
||
|
|
||
|
|
||
|
def print_feature_descs(layer, feature_dir):
|
||
|
"""
|
||
|
Print the feature descriptions for the features in feature_dir.
|
||
|
"""
|
||
|
kernel_files_features = os.path.join(layer, "recipes-kernel/linux/files/" +
|
||
|
feature_dir)
|
||
|
for root, dirs, files in os.walk(kernel_files_features):
|
||
|
for file in files:
|
||
|
if file.endswith("~") or file.endswith("#"):
|
||
|
continue
|
||
|
if file.endswith(".scc"):
|
||
|
fullpath = os.path.join(layer, "recipes-kernel/linux/files/" +
|
||
|
feature_dir + "/" + file)
|
||
|
f = open(fullpath)
|
||
|
feature_desc = find_feature_desc(f.readlines())
|
||
|
print(feature_dir + "/" + file + ": " + feature_desc)
|
||
|
|
||
|
|
||
|
def yocto_kernel_available_features_list(scripts_path, machine):
|
||
|
"""
|
||
|
Display the list of all the kernel features available for use in
|
||
|
BSPs, as gathered from the set of feature sources.
|
||
|
"""
|
||
|
layer = find_bsp_layer(machine)
|
||
|
kernel = find_current_kernel(layer, machine)
|
||
|
if not kernel:
|
||
|
print("Couldn't determine the kernel for this BSP, exiting.")
|
||
|
sys.exit(1)
|
||
|
|
||
|
context = create_context(machine, "arch", scripts_path)
|
||
|
context["name"] = "name"
|
||
|
context["filename"] = kernel
|
||
|
giturl = find_giturl(context)
|
||
|
feature_url = find_feature_url(giturl)
|
||
|
|
||
|
feature_cmd = "wget -q -O - " + feature_url
|
||
|
tmp = subprocess.Popen(feature_cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
|
||
|
|
||
|
print("The current set of kernel features available to %s is:\n" % machine)
|
||
|
|
||
|
if tmp:
|
||
|
tmpline = tmp.split("\n")
|
||
|
in_kernel_options = False
|
||
|
for line in tmpline:
|
||
|
if not "=" in line:
|
||
|
if in_kernel_options:
|
||
|
break
|
||
|
if "kernel-options" in line:
|
||
|
in_kernel_options = True
|
||
|
continue
|
||
|
if in_kernel_options:
|
||
|
feature_def = line.split("=")
|
||
|
feature_type = feature_def[0].strip()
|
||
|
feature = feature_def[1].strip()
|
||
|
desc = get_feature_desc(giturl, feature)
|
||
|
print("%s: %s" % (feature, desc))
|
||
|
|
||
|
print("[local]")
|
||
|
|
||
|
print_feature_descs(layer, "cfg")
|
||
|
print_feature_descs(layer, "features")
|
||
|
|
||
|
|
||
|
def find_feature_desc_url(git_url, feature):
|
||
|
"""
|
||
|
Find the url of the kernel feature in the kernel repo specified
|
||
|
from the BSP's kernel recipe SRC_URI.
|
||
|
"""
|
||
|
feature_desc_url = ""
|
||
|
if git_url.startswith("git://"):
|
||
|
git_url = git_url[len("git://"):].strip()
|
||
|
s = git_url.split("/")
|
||
|
if s[1].endswith(".git"):
|
||
|
s[1] = s[1][:len(s[1]) - len(".git")]
|
||
|
feature_desc_url = "http://" + s[0] + "/cgit/cgit.cgi/" + s[1] + \
|
||
|
"/plain/meta/cfg/kernel-cache/" + feature + "?h=meta"
|
||
|
|
||
|
return feature_desc_url
|
||
|
|
||
|
|
||
|
def get_feature_desc(git_url, feature):
|
||
|
"""
|
||
|
Return a feature description of the form 'description [compatibility]
|
||
|
BSPs, as gathered from the set of feature sources.
|
||
|
"""
|
||
|
feature_desc_url = find_feature_desc_url(git_url, feature)
|
||
|
feature_desc_cmd = "wget -q -O - " + feature_desc_url
|
||
|
tmp = subprocess.Popen(feature_desc_cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
|
||
|
|
||
|
return find_feature_desc(tmp.split("\n"))
|
||
|
|
||
|
|
||
|
def yocto_kernel_feature_describe(scripts_path, machine, feature):
|
||
|
"""
|
||
|
Display the description of a specific kernel feature available for
|
||
|
use in a BSP.
|
||
|
"""
|
||
|
layer = find_bsp_layer(machine)
|
||
|
|
||
|
kernel = find_current_kernel(layer, machine)
|
||
|
if not kernel:
|
||
|
print("Couldn't determine the kernel for this BSP, exiting.")
|
||
|
sys.exit(1)
|
||
|
|
||
|
context = create_context(machine, "arch", scripts_path)
|
||
|
context["name"] = "name"
|
||
|
context["filename"] = kernel
|
||
|
giturl = find_giturl(context)
|
||
|
|
||
|
desc = get_feature_desc(giturl, feature)
|
||
|
|
||
|
print(desc)
|
||
|
|
||
|
|
||
|
def check_feature_name(feature_name):
|
||
|
"""
|
||
|
Sanity-check the feature name for create/destroy. Return False if not OK.
|
||
|
"""
|
||
|
if not feature_name.endswith(".scc"):
|
||
|
print("Invalid feature name (must end with .scc) [%s], exiting" % feature_name)
|
||
|
return False
|
||
|
|
||
|
if "/" in feature_name:
|
||
|
print("Invalid feature name (don't specify directory) [%s], exiting" % feature_name)
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
def check_create_input(feature_items):
|
||
|
"""
|
||
|
Sanity-check the create input. Return False if not OK.
|
||
|
"""
|
||
|
if not check_feature_name(feature_items[0]):
|
||
|
return False
|
||
|
|
||
|
if feature_items[1].endswith(".patch") or feature_items[1].startswith("CONFIG_"):
|
||
|
print("Missing description and/or compatibilty [%s], exiting" % feature_items[1])
|
||
|
return False
|
||
|
|
||
|
if feature_items[2].endswith(".patch") or feature_items[2].startswith("CONFIG_"):
|
||
|
print("Missing description and/or compatibility [%s], exiting" % feature_items[1])
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
def yocto_kernel_feature_create(scripts_path, machine, feature_items):
|
||
|
"""
|
||
|
Create a recipe-space kernel feature in a BSP.
|
||
|
"""
|
||
|
if not check_create_input(feature_items):
|
||
|
sys.exit(1)
|
||
|
|
||
|
feature = feature_items[0]
|
||
|
feature_basename = feature.split(".")[0]
|
||
|
feature_description = feature_items[1]
|
||
|
feature_compat = feature_items[2]
|
||
|
|
||
|
patches = []
|
||
|
cfg_items = []
|
||
|
|
||
|
for item in feature_items[3:]:
|
||
|
if item.endswith(".patch"):
|
||
|
patches.append(item)
|
||
|
elif item.startswith("CONFIG"):
|
||
|
if ("=y" in item or "=m" in item):
|
||
|
cfg_items.append(item)
|
||
|
else:
|
||
|
print("Invalid feature item (must be .patch or CONFIG_*) [%s], exiting" % item)
|
||
|
sys.exit(1)
|
||
|
|
||
|
feature_dirname = "cfg"
|
||
|
if patches:
|
||
|
feature_dirname = "features"
|
||
|
|
||
|
filesdir = find_filesdir(scripts_path, machine)
|
||
|
if not filesdir:
|
||
|
print("Couldn't add feature (%s), no 'files' dir found" % feature)
|
||
|
sys.exit(1)
|
||
|
|
||
|
featdir = os.path.join(filesdir, feature_dirname)
|
||
|
if not os.path.exists(featdir):
|
||
|
os.mkdir(featdir)
|
||
|
|
||
|
for patch in patches:
|
||
|
if not os.path.isfile(patch):
|
||
|
print("Couldn't find patch (%s), exiting" % patch)
|
||
|
sys.exit(1)
|
||
|
basename = os.path.basename(patch)
|
||
|
featdir_patch = os.path.join(featdir, basename)
|
||
|
shutil.copyfile(patch, featdir_patch)
|
||
|
|
||
|
new_cfg_filename = os.path.join(featdir, feature_basename + ".cfg")
|
||
|
new_cfg_file = open(new_cfg_filename, "w")
|
||
|
for cfg_item in cfg_items:
|
||
|
new_cfg_file.write(cfg_item + "\n")
|
||
|
new_cfg_file.close()
|
||
|
|
||
|
new_feature_filename = os.path.join(featdir, feature_basename + ".scc")
|
||
|
new_feature_file = open(new_feature_filename, "w")
|
||
|
new_feature_file.write("define KFEATURE_DESCRIPTION \"" + feature_description + "\"\n")
|
||
|
new_feature_file.write("define KFEATURE_COMPATIBILITY " + feature_compat + "\n\n")
|
||
|
|
||
|
for patch in patches:
|
||
|
patch_dir, patch_file = os.path.split(patch)
|
||
|
new_feature_file.write("patch " + patch_file + "\n")
|
||
|
|
||
|
new_feature_file.write("kconf non-hardware " + feature_basename + ".cfg\n")
|
||
|
new_feature_file.close()
|
||
|
|
||
|
print("Added feature:")
|
||
|
print("\t%s" % feature_dirname + "/" + feature)
|
||
|
|
||
|
|
||
|
def feature_in_use(scripts_path, machine, feature):
|
||
|
"""
|
||
|
Determine whether the specified feature is in use by the BSP.
|
||
|
Return True if so, False otherwise.
|
||
|
"""
|
||
|
features = read_features(scripts_path, machine)
|
||
|
for f in features:
|
||
|
if f == feature:
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
|
||
|
def feature_remove(scripts_path, machine, feature):
|
||
|
"""
|
||
|
Remove the specified feature from the available recipe-space
|
||
|
features defined for the BSP.
|
||
|
"""
|
||
|
features = read_features(scripts_path, machine)
|
||
|
new_features = []
|
||
|
for f in features:
|
||
|
if f == feature:
|
||
|
continue
|
||
|
new_features.append(f)
|
||
|
write_features(scripts_path, machine, new_features)
|
||
|
|
||
|
|
||
|
def yocto_kernel_feature_destroy(scripts_path, machine, feature):
|
||
|
"""
|
||
|
Remove a recipe-space kernel feature from a BSP.
|
||
|
"""
|
||
|
if not check_feature_name(feature):
|
||
|
sys.exit(1)
|
||
|
|
||
|
if feature_in_use(scripts_path, machine, "features/" + feature) or \
|
||
|
feature_in_use(scripts_path, machine, "cfg/" + feature):
|
||
|
print("Feature %s is in use (use 'feature rm' to un-use it first), exiting" % feature)
|
||
|
sys.exit(1)
|
||
|
|
||
|
filesdir = find_filesdir(scripts_path, machine)
|
||
|
if not filesdir:
|
||
|
print("Couldn't destroy feature (%s), no 'files' dir found" % feature)
|
||
|
sys.exit(1)
|
||
|
|
||
|
feature_dirname = "features"
|
||
|
featdir = os.path.join(filesdir, feature_dirname)
|
||
|
if not os.path.exists(featdir):
|
||
|
print("Couldn't find feature directory (%s)" % feature_dirname)
|
||
|
sys.exit(1)
|
||
|
|
||
|
feature_fqn = os.path.join(featdir, feature)
|
||
|
if not os.path.exists(feature_fqn):
|
||
|
feature_dirname = "cfg"
|
||
|
featdir = os.path.join(filesdir, feature_dirname)
|
||
|
if not os.path.exists(featdir):
|
||
|
print("Couldn't find feature directory (%s)" % feature_dirname)
|
||
|
sys.exit(1)
|
||
|
feature_fqn = os.path.join(featdir, feature_filename)
|
||
|
if not os.path.exists(feature_fqn):
|
||
|
print("Couldn't find feature (%s)" % feature)
|
||
|
sys.exit(1)
|
||
|
|
||
|
f = open(feature_fqn, "r")
|
||
|
lines = f.readlines()
|
||
|
for line in lines:
|
||
|
s = line.strip()
|
||
|
if s.startswith("patch ") or s.startswith("kconf "):
|
||
|
split_line = s.split()
|
||
|
filename = os.path.join(featdir, split_line[-1])
|
||
|
if os.path.exists(filename):
|
||
|
os.remove(filename)
|
||
|
f.close()
|
||
|
os.remove(feature_fqn)
|
||
|
|
||
|
feature_remove(scripts_path, machine, feature)
|
||
|
|
||
|
print("Removed feature:")
|
||
|
print("\t%s" % feature_dirname + "/" + feature)
|
||
|
|
||
|
|
||
|
def base_branches(context):
|
||
|
"""
|
||
|
Return a list of the base branches found in the kernel git repo.
|
||
|
"""
|
||
|
giturl = find_giturl(context)
|
||
|
|
||
|
print("Getting branches from remote repo %s..." % giturl)
|
||
|
|
||
|
gitcmd = "git ls-remote %s *heads* 2>&1" % (giturl)
|
||
|
tmp = subprocess.Popen(gitcmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
|
||
|
|
||
|
branches = []
|
||
|
|
||
|
if tmp:
|
||
|
tmpline = tmp.split("\n")
|
||
|
for line in tmpline:
|
||
|
if len(line)==0:
|
||
|
break;
|
||
|
if not line.endswith("base"):
|
||
|
continue;
|
||
|
idx = line.find("refs/heads/")
|
||
|
kbranch = line[idx + len("refs/heads/"):]
|
||
|
if kbranch.find("/") == -1 and kbranch.find("base") == -1:
|
||
|
continue
|
||
|
idx = kbranch.find("base")
|
||
|
branches.append(kbranch[:idx - 1])
|
||
|
|
||
|
return branches
|
||
|
|
||
|
|
||
|
def all_branches(context):
|
||
|
"""
|
||
|
Return a list of all the branches found in the kernel git repo.
|
||
|
"""
|
||
|
giturl = find_giturl(context)
|
||
|
|
||
|
print("Getting branches from remote repo %s..." % giturl)
|
||
|
|
||
|
gitcmd = "git ls-remote %s *heads* 2>&1" % (giturl)
|
||
|
tmp = subprocess.Popen(gitcmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
|
||
|
|
||
|
branches = []
|
||
|
|
||
|
base_prefixes = None
|
||
|
|
||
|
try:
|
||
|
branches_base = context["branches_base"]
|
||
|
if branches_base:
|
||
|
base_prefixes = branches_base.split(":")
|
||
|
except KeyError:
|
||
|
pass
|
||
|
|
||
|
arch = context["arch"]
|
||
|
|
||
|
if tmp:
|
||
|
tmpline = tmp.split("\n")
|
||
|
for line in tmpline:
|
||
|
if len(line)==0:
|
||
|
break;
|
||
|
idx = line.find("refs/heads/")
|
||
|
kbranch = line[idx + len("refs/heads/"):]
|
||
|
kbranch_prefix = kbranch.rsplit("/", 1)[0]
|
||
|
|
||
|
if base_prefixes:
|
||
|
for base_prefix in base_prefixes:
|
||
|
if kbranch_prefix == base_prefix:
|
||
|
branches.append(kbranch)
|
||
|
continue
|
||
|
|
||
|
if (kbranch.find("/") != -1 and
|
||
|
(kbranch.find("standard") != -1 or kbranch.find("base") != -1) or
|
||
|
kbranch == "base"):
|
||
|
branches.append(kbranch)
|
||
|
continue
|
||
|
|
||
|
return branches
|