1# Recipe creation tool - kernel module support plugin
2#
3# Copyright (C) 2016 Intel Corporation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18import re
19import logging
20from recipetool.create import RecipeHandler, read_pkgconfig_provides, validate_pv
21
22logger = logging.getLogger('recipetool')
23
24tinfoil = None
25
26def tinfoil_init(instance):
27    global tinfoil
28    tinfoil = instance
29
30
31class KernelModuleRecipeHandler(RecipeHandler):
32    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
33        import bb.process
34        if 'buildsystem' in handled:
35            return False
36
37        module_inc_re = re.compile(r'^#include\s+<linux/module.h>$')
38        makefiles = []
39        is_module = False
40
41        makefiles = []
42
43        files = RecipeHandler.checkfiles(srctree, ['*.c', '*.h'], recursive=True, excludedirs=['contrib', 'test', 'examples'])
44        if files:
45            for cfile in files:
46                # Look in same dir or parent for Makefile
47                for makefile in [os.path.join(os.path.dirname(cfile), 'Makefile'), os.path.join(os.path.dirname(os.path.dirname(cfile)), 'Makefile')]:
48                    if makefile in makefiles:
49                        break
50                    else:
51                        if os.path.exists(makefile):
52                            makefiles.append(makefile)
53                            break
54                else:
55                    continue
56                with open(cfile, 'r', errors='surrogateescape') as f:
57                    for line in f:
58                        if module_inc_re.match(line.strip()):
59                            is_module = True
60                            break
61                if is_module:
62                    break
63
64        if is_module:
65            classes.append('module')
66            handled.append('buildsystem')
67            # module.bbclass and the classes it inherits do most of the hard
68            # work, but we need to tweak it slightly depending on what the
69            # Makefile does (and there is a range of those)
70            # Check the makefile for the appropriate install target
71            install_lines = []
72            compile_lines = []
73            in_install = False
74            in_compile = False
75            install_target = None
76            with open(makefile, 'r', errors='surrogateescape') as f:
77                for line in f:
78                    if line.startswith('install:'):
79                        if not install_lines:
80                            in_install = True
81                            install_target = 'install'
82                    elif line.startswith('modules_install:'):
83                        install_lines = []
84                        in_install = True
85                        install_target = 'modules_install'
86                    elif line.startswith('modules:'):
87                        compile_lines = []
88                        in_compile = True
89                    elif line.startswith(('all:', 'default:')):
90                        if not compile_lines:
91                            in_compile = True
92                    elif line:
93                        if line[0] == '\t':
94                            if in_install:
95                                install_lines.append(line)
96                            elif in_compile:
97                                compile_lines.append(line)
98                        elif ':' in line:
99                            in_install = False
100                            in_compile = False
101
102            def check_target(lines, install):
103                kdirpath = ''
104                manual_install = False
105                for line in lines:
106                    splitline = line.split()
107                    if splitline[0] in ['make', 'gmake', '$(MAKE)']:
108                        if '-C' in splitline:
109                            idx = splitline.index('-C') + 1
110                            if idx < len(splitline):
111                                kdirpath = splitline[idx]
112                                break
113                    elif install and splitline[0] == 'install':
114                        if '.ko' in line:
115                            manual_install = True
116                return kdirpath, manual_install
117
118            kdirpath = None
119            manual_install = False
120            if install_lines:
121                kdirpath, manual_install = check_target(install_lines, install=True)
122            if compile_lines and not kdirpath:
123                kdirpath, _ = check_target(compile_lines, install=False)
124
125            if manual_install or not install_lines:
126                lines_after.append('EXTRA_OEMAKE_append_task-install = " -C ${STAGING_KERNEL_DIR} M=${S}"')
127            elif install_target and install_target != 'modules_install':
128                lines_after.append('MODULES_INSTALL_TARGET = "install"')
129
130            warnmsg = None
131            kdirvar = None
132            if kdirpath:
133                res = re.match(r'\$\(([^$)]+)\)', kdirpath)
134                if res:
135                    kdirvar = res.group(1)
136                    if kdirvar != 'KERNEL_SRC':
137                        lines_after.append('EXTRA_OEMAKE += "%s=${STAGING_KERNEL_DIR}"' % kdirvar)
138                elif kdirpath.startswith('/lib/'):
139                    warnmsg = 'Kernel path in install makefile is hardcoded - you will need to patch the makefile'
140            if not kdirvar and not warnmsg:
141                warnmsg = 'Unable to find means of passing kernel path into install makefile - if kernel path is hardcoded you will need to patch the makefile'
142            if warnmsg:
143                warnmsg += '. Note that the variable KERNEL_SRC will be passed in as the kernel source path.'
144                logger.warning(warnmsg)
145                lines_after.append('# %s' % warnmsg)
146
147            return True
148
149        return False
150
151def register_recipe_handlers(handlers):
152    handlers.append((KernelModuleRecipeHandler(), 15))
153