3 # Generate the output tree into a specified directory.
6 import argparse
, sys
, os
, errno
, shutil
, re
, subprocess
9 source_dir
= os
.path
.abspath(os
.path
.dirname(__file__
))
10 sys
.path
.append(source_dir
)
11 # and import libraries we have
12 from lib
import kconfig
, patch
, make
13 from lib
import bpgit
as git
15 def read_copy_list(copyfile
):
17 Read a copy-list file and return a list of (source, target)
18 tuples. The source and target are usually the same, but in
19 the copy-list file there may be a rename included.
23 # remove leading/trailing whitespace
26 if not item
or item
[0] == '#':
29 raise Exception("Input path '%s' is absolute path, this isn't allowed" % (item
, ))
31 srcitem
, dstitem
= item
.split(' -> ')
32 if (srcitem
[-1] == '/') != (dstitem
[-1] == '/'):
33 raise Exception("Cannot copy file/dir to dir/file")
35 srcitem
= dstitem
= item
36 ret
.append((srcitem
, dstitem
))
40 def read_dependencies(depfilename
):
42 Read a (the) dependency file and return the list of
43 dependencies as a dictionary, mapping a Kconfig symbol
44 to a list of kernel version dependencies. While reading
45 ignore blank/commented lines.
48 depfile
= open(depfilename
, 'r')
51 if not item
or item
[0] == '#':
53 sym
, dep
= item
.split()
61 def check_output_dir(d
, clean
):
63 Check that the output directory doesn't exist or is empty,
64 unless clean is True in which case it's nuked. This helps
65 sanity check the output when generating a tree, so usually
66 running with --clean isn't suggested.
69 shutil
.rmtree(d
, ignore_errors
=True)
73 if e
.errno
!= errno
.ENOENT
:
77 def copytree(src
, dst
, symlinks
=False, ignore
=None):
79 Copy a directory tree. This differs from shutil.copytree()
80 in that it allows destination directories to already exist.
82 names
= os
.listdir(src
)
83 if ignore
is not None:
84 ignored_names
= ignore(src
, names
)
88 if not os
.path
.isdir(dst
):
92 if name
in ignored_names
:
94 srcname
= os
.path
.join(src
, name
)
95 dstname
= os
.path
.join(dst
, name
)
97 if symlinks
and os
.path
.islink(srcname
):
98 linkto
= os
.readlink(srcname
)
99 os
.symlink(linkto
, dstname
)
100 elif os
.path
.isdir(srcname
):
101 copytree(srcname
, dstname
, symlinks
, ignore
)
103 shutil
.copy2(srcname
, dstname
)
104 except (IOError, os
.error
) as why
:
105 errors
.append((srcname
, dstname
, str(why
)))
106 # catch the Error from the recursive copytree so that we can
107 # continue with other files
108 except shutil
.Error
as err
:
109 errors
.extend(err
.args
[0])
111 shutil
.copystat(src
, dst
)
113 # can't copy file access times on Windows
115 except OSError as why
:
116 errors
.extend((src
, dst
, str(why
)))
118 raise shutil
.Error(errors
)
121 def copy_files(srcpath
, copy_list
, outdir
):
123 Copy the copy_list files and directories from the srcpath
124 to the outdir. The copy_list contains source and target
127 For now, it also ignores any *~ editor backup files, though
128 this should probably be generalized (maybe using .gitignore?)
129 Similarly the code that only copies some files (*.c, *.h,
130 *.awk, Kconfig, Makefile) to avoid any build remnants in the
131 kernel if they should exist.
133 for srcitem
, tgtitem
in copy_list
:
135 copytree(srcpath
, outdir
, ignore
=shutil
.ignore_patterns('*~'))
136 elif tgtitem
[-1] == '/':
137 def copy_ignore(dir, entries
):
140 if i
[-2:] == '.o' or i
[-1] == '~':
143 copytree(os
.path
.join(srcpath
, srcitem
),
144 os
.path
.join(outdir
, tgtitem
),
148 os
.makedirs(os
.path
.join(outdir
, os
.path
.dirname(tgtitem
)))
150 # ignore dirs we might have created just now
151 if e
.errno
!= errno
.EEXIST
:
153 shutil
.copy(os
.path
.join(srcpath
, srcitem
),
154 os
.path
.join(outdir
, tgtitem
))
157 def copy_git_files(srcpath
, copy_list
, rev
, outdir
):
159 "Copy" files from a git repository. This really means listing them with
160 ls-tree and then using git show to obtain all the blobs.
162 for srcitem
, tgtitem
in copy_list
:
163 for m
, t
, h
, f
in git
.ls_tree(rev
=rev
, files
=(srcitem
,), tree
=srcpath
):
165 f
= os
.path
.join(outdir
, f
.replace(srcitem
, tgtitem
))
166 d
= os
.path
.dirname(f
)
167 if not os
.path
.exists(d
):
170 git
.get_blob(h
, outf
, tree
=srcpath
)
172 os
.chmod(f
, int(m
, 8))
174 def automatic_backport_mangle_c_file(name
):
175 return name
.replace('/', '-')
178 def add_automatic_backports(args
):
180 export
= re
.compile(r
'^EXPORT_SYMBOL(_GPL)?\((?P<sym>[^\)]*)\)')
181 bpi
= kconfig
.get_backport_info(os
.path
.join(args
.outdir
, 'compat', 'Kconfig'))
182 configtree
= kconfig
.ConfigTree(os
.path
.join(args
.outdir
, 'Kconfig'))
183 all_selects
= configtree
.all_selects()
184 for sym
, vals
in bpi
.items():
185 if sym
.startswith('BACKPORT_BUILD_'):
186 if not sym
[15:] in all_selects
:
187 disable_list
.append(sym
)
189 symtype
, module_name
, c_files
, h_files
= vals
194 files
.append((f
, os
.path
.join('compat', automatic_backport_mangle_c_file(f
))))
196 files
.append((os
.path
.join('include', f
),
197 os
.path
.join('include', os
.path
.dirname(f
), 'backport-' + os
.path
.basename(f
))))
198 if args
.git_revision
:
199 copy_git_files(args
.kerneldir
, files
, args
.git_revision
, args
.outdir
)
201 copy_files(args
.kerneldir
, files
, args
.outdir
)
203 # now add the Makefile line
204 mf
= open(os
.path
.join(args
.outdir
, 'compat', 'Makefile'), 'a+')
205 o_files
= [automatic_backport_mangle_c_file(f
)[:-1] + 'o' for f
in c_files
]
206 if symtype
== 'tristate':
208 raise Exception('backporting a module requires a #module-name')
210 mf
.write('%s-objs += %s\n' % (module_name
, of
))
211 mf
.write('obj-$(CPTCFG_%s) += %s.o\n' % (sym
, module_name
))
212 elif symtype
== 'bool':
213 mf
.write('compat-$(CPTCFG_%s) += %s\n' % (sym
, ' '.join(o_files
)))
215 # finally create the include file
218 for l
in open(os
.path
.join(args
.outdir
, 'compat',
219 automatic_backport_mangle_c_file(f
)), 'r'):
222 syms
.append(m
.group('sym'))
224 outf
= open(os
.path
.join(args
.outdir
, 'include', f
), 'w')
225 outf
.write('/* Automatically created during backport process */\n')
226 outf
.write('#ifndef CPTCFG_%s\n' % sym
)
227 outf
.write('#include_next <%s>\n' % f
)
228 outf
.write('#else\n');
230 outf
.write('#undef %s\n' % s
)
231 outf
.write('#define %s LINUX_BACKPORT(%s)\n' % (s
, s
))
232 outf
.write('#include <%s>\n' % (os
.path
.dirname(f
) + '/backport-' + os
.path
.basename(f
), ))
233 outf
.write('#endif /* CPTCFG_%s */\n' % sym
)
236 def git_debug_init(args
):
238 Initialize a git repository in the output directory and commit the current
239 code in it. This is only used for debugging the transformations this code
240 will do to the output later.
242 if not args
.gitdebug
:
244 git
.init(tree
=args
.outdir
)
245 git
.commit_all("Copied backport", tree
=args
.outdir
)
248 def git_debug_snapshot(args
, name
):
250 Take a git snapshot for the debugging.
252 if not args
.gitdebug
:
254 git
.commit_all(name
, tree
=args
.outdir
)
258 # set up and parse arguments
259 parser
= argparse
.ArgumentParser(description
='generate backport tree')
260 parser
.add_argument('kerneldir', metavar
='<kernel tree>', type=str,
261 help='Kernel tree to copy drivers from')
262 parser
.add_argument('outdir', metavar
='<output directory>', type=str,
263 help='Directory to write the generated tree to')
264 parser
.add_argument('--copy-list', metavar
='<listfile>', type=argparse
.FileType('r'),
266 help='File containing list of files/directories to copy, default "copy-list"')
267 parser
.add_argument('--git-revision', metavar
='<revision>', type=str,
268 help='git commit revision (see gitrevisions(7)) to take objects from.' +
269 'If this is specified, the kernel tree is used as git object storage ' +
270 'and we use git ls-tree to get the files.')
271 parser
.add_argument('--clean', const
=True, default
=False, action
="store_const",
272 help='Clean output directory instead of erroring if it isn\'t empty')
273 parser
.add_argument('--refresh', const
=True, default
=False, action
="store_const",
274 help='Refresh patches as they are applied, the source dir will be modified!')
275 parser
.add_argument('--base-name', metavar
='<name>', type=str, default
='Linux',
276 help='name of base tree, default just "Linux"')
277 parser
.add_argument('--gitdebug', const
=True, default
=False, action
="store_const",
278 help='Use git, in the output tree, to debug the various transformation steps ' +
279 'that the tree generation makes (apply patches, ...)')
280 parser
.add_argument('--verbose', const
=True, default
=False, action
="store_const",
281 help='Print more verbose information')
282 parser
.add_argument('--extra-driver', nargs
=2, metavar
=('<source dir>', '<copy-list>'), type=str,
283 action
='append', default
=[], help='Extra driver directory/copy-list.')
284 args
= parser
.parse_args()
287 sys
.stdout
.write(msg
)
288 sys
.stdout
.write('\n')
291 return process(args
.kerneldir
, args
.outdir
, args
.copy_list
,
292 git_revision
=args
.git_revision
, clean
=args
.clean
,
293 refresh
=args
.refresh
, base_name
=args
.base_name
,
294 gitdebug
=args
.gitdebug
, verbose
=args
.verbose
,
295 extra_driver
=args
.extra_driver
, logwrite
=logwrite
)
297 def process(kerneldir
, outdir
, copy_list_file
, git_revision
=None,
298 clean
=False, refresh
=False, base_name
="Linux", gitdebug
=False,
299 verbose
=False, extra_driver
=[], logwrite
=lambda x
:None,
300 git_tracked_version
=False):
302 def __init__(self
, kerneldir
, outdir
, copy_list_file
,
303 git_revision
, clean
, refresh
, base_name
,
304 gitdebug
, verbose
, extra_driver
):
305 self
.kerneldir
= kerneldir
307 self
.copy_list
= copy_list_file
308 self
.git_revision
= git_revision
310 self
.refresh
= refresh
311 self
.base_name
= base_name
312 self
.gitdebug
= gitdebug
313 self
.verbose
= verbose
314 self
.extra_driver
= extra_driver
315 args
= Args(kerneldir
, outdir
, copy_list_file
,
316 git_revision
, clean
, refresh
, base_name
,
317 gitdebug
, verbose
, extra_driver
)
318 # start processing ...
320 copy_list
= read_copy_list(args
.copy_list
)
321 deplist
= read_dependencies(os
.path
.join(source_dir
, 'dependencies'))
323 # validate output directory
324 check_output_dir(args
.outdir
, args
.clean
)
327 backport_files
= [(x
, x
) for x
in [
328 'Kconfig', 'Makefile', 'Makefile.build', 'Makefile.kernel', '.gitignore',
329 'Makefile.real', 'compat/', 'backport-include/', 'kconf/', 'defconfigs/',
330 'scripts/', '.blacklist.map', 'udev/',
332 if not args
.git_revision
:
333 logwrite('Copy original source files ...')
335 logwrite('Get original source files from git ...')
337 copy_files(os
.path
.join(source_dir
, 'backport'), backport_files
, args
.outdir
)
341 if not args
.git_revision
:
342 copy_files(args
.kerneldir
, copy_list
, args
.outdir
)
344 copy_git_files(args
.kerneldir
, copy_list
, args
.git_revision
, args
.outdir
)
346 # FIXME: should we add a git version of this (e.g. --git-extra-driver)?
347 for src
, copy_list
in args
.extra_driver
:
348 copy_files(src
, read_copy_list(open(copy_list
, 'r')), args
.outdir
)
350 git_debug_snapshot(args
, 'Add driver sources')
352 disable_list
= add_automatic_backports(args
)
354 bpcfg
= kconfig
.ConfigTree(os
.path
.join(args
.outdir
, 'compat', 'Kconfig'))
355 bpcfg
.disable_symbols(disable_list
)
356 git_debug_snapshot(args
, 'Add automatic backports')
358 logwrite('Apply patches ...')
360 for root
, dirs
, files
in os
.walk(os
.path
.join(source_dir
, 'patches')):
362 if f
.endswith('.patch'):
363 patches
.append(os
.path
.join(root
, f
))
365 prefix_len
= len(os
.path
.join(source_dir
, 'patches')) + 1
366 for pfile
in patches
:
367 print_name
= pfile
[prefix_len
:]
368 # read the patch file
369 p
= patch
.fromfile(pfile
)
370 # complain if it's not a patch
372 raise Exception('No patch content found in %s' % print_name
)
373 # leading / seems to be stripped?
374 if 'dev/null' in p
.items
[0].source
:
375 raise Exception('Patches creating files are not supported (in %s)' % print_name
)
376 # check if the first file the patch touches exists, if so
377 # assume the patch needs to be applied -- otherwise continue
378 patched_file
= '/'.join(p
.items
[0].source
.split('/')[1:])
379 fullfn
= os
.path
.join(args
.outdir
, patched_file
)
380 if not os
.path
.exists(fullfn
):
382 logwrite("Not applying %s, not needed" % print_name
)
385 logwrite("Applying patch %s" % print_name
)
388 # but for refresh, of course look at all files the patch touches
389 for patchitem
in p
.items
:
390 patched_file
= '/'.join(patchitem
.source
.split('/')[1:])
391 fullfn
= os
.path
.join(args
.outdir
, patched_file
)
392 shutil
.copyfile(fullfn
, fullfn
+ '.orig_file')
394 process
= subprocess
.Popen(['patch', '-p1'], stdout
=subprocess
.PIPE
,
395 stderr
=subprocess
.STDOUT
, stdin
=subprocess
.PIPE
,
396 close_fds
=True, universal_newlines
=True,
398 output
= process
.communicate(input=open(pfile
, 'r').read())[0]
399 output
= output
.split('\n')
404 logwrite('> %s' % line
)
405 if process
.returncode
!= 0:
407 logwrite("Failed to apply changes from %s" % print_name
)
409 logwrite('> %s' % line
)
413 pfilef
= open(pfile
+ '.tmp', 'a')
414 pfilef
.write(p
.top_header
)
416 for patchitem
in p
.items
:
417 patched_file
= '/'.join(patchitem
.source
.split('/')[1:])
418 fullfn
= os
.path
.join(args
.outdir
, patched_file
)
419 process
= subprocess
.Popen(['diff', '-p', '-u', patched_file
+ '.orig_file', patched_file
,
420 '--label', 'a/' + patched_file
,
421 '--label', 'b/' + patched_file
],
422 stdout
=pfilef
, close_fds
=True,
423 universal_newlines
=True, cwd
=args
.outdir
)
425 os
.unlink(fullfn
+ '.orig_file')
426 if not process
.returncode
in (0, 1):
427 logwrite("Failed to diff to refresh %s" % print_name
)
429 os
.unlink(pfile
+ '.tmp')
432 os
.rename(pfile
+ '.tmp', pfile
)
434 # remove orig/rej files that patch sometimes creates
435 for root
, dirs
, files
in os
.walk(args
.outdir
):
437 if f
[-5:] == '.orig' or f
[-4:] == '.rej':
438 os
.unlink(os
.path
.join(root
, f
))
439 git_debug_snapshot(args
, "apply backport patch %s" % print_name
)
441 # some post-processing is required
442 configtree
= kconfig
.ConfigTree(os
.path
.join(args
.outdir
, 'Kconfig'))
443 logwrite('Modify Kconfig tree ...')
444 configtree
.prune_sources(ignore
=['Kconfig.kernel', 'Kconfig.versions'])
445 git_debug_snapshot(args
, "prune Kconfig tree")
446 configtree
.force_tristate_modular()
447 git_debug_snapshot(args
, "force tristate options modular")
448 configtree
.modify_selects()
449 git_debug_snapshot(args
, "convert select to depends on")
451 # write the versioning file
452 if git_tracked_version
:
453 backports_version
= "(see git)"
454 kernel_version
= "(see git)"
456 backports_version
= git
.describe(tree
=source_dir
)
457 kernel_version
= git
.describe(rev
=args
.git_revision
or 'HEAD',
459 f
= open(os
.path
.join(args
.outdir
, 'versions'), 'w')
460 f
.write('BACKPORTS_VERSION="%s"\n' % backports_version
)
461 f
.write('BACKPORTED_KERNEL_VERSION="%s"\n' % kernel_version
)
462 f
.write('BACKPORTED_KERNEL_NAME="%s"\n' % args
.base_name
)
463 if git_tracked_version
:
464 f
.write('BACKPORTS_GIT_TRACKED="backport tracker ID: $(shell git rev-parse HEAD 2>/dev/null || echo \'not built in git tree\')"\n')
467 symbols
= configtree
.symbols()
469 # write local symbol list -- needed during build
470 f
= open(os
.path
.join(args
.outdir
, '.local-symbols'), 'w')
472 f
.write('%s=\n' % sym
)
475 git_debug_snapshot(args
, "add versions/symbols files")
477 logwrite('Rewrite Makefiles and Kconfig files ...')
479 # rewrite Makefile and source symbols
481 for some_symbols
in [symbols
[i
:i
+ 50] for i
in range(0, len(symbols
), 50)]:
482 r
= 'CONFIG_((' + '|'.join([s
+ '(_MODULE)?' for s
in some_symbols
]) + ')([^A-Za-z0-9_]|$))'
483 regexes
.append(re
.compile(r
, re
.MULTILINE
))
484 for root
, dirs
, files
in os
.walk(args
.outdir
):
485 # don't go into .git dir (possible debug thing)
489 data
= open(os
.path
.join(root
, f
), 'r').read()
491 data
= r
.sub(r
'CPTCFG_\1', data
)
492 data
= re
.sub(r
'\$\(srctree\)', '$(backport_srctree)', data
)
493 data
= re
.sub(r
'-Idrivers', '-I$(backport_srctree)/drivers', data
)
494 fo
= open(os
.path
.join(root
, f
), 'w')
498 git_debug_snapshot(args
, "rename config symbol / srctree usage")
500 # disable unbuildable Kconfig symbols and stuff Makefiles that doesn't exist
501 maketree
= make
.MakeTree(os
.path
.join(args
.outdir
, 'Makefile.kernel'))
503 disable_makefile
= []
504 for sym
in maketree
.get_impossible_symbols():
505 disable_kconfig
.append(sym
[7:])
506 disable_makefile
.append(sym
[7:])
508 configtree
.disable_symbols(disable_kconfig
)
509 git_debug_snapshot(args
, "disable impossible kconfig symbols")
511 # add kernel version dependencies to Kconfig, from the dependency list
513 for sym
in tuple(deplist
.keys()):
515 for dep
in deplist
[sym
]:
517 new
.append('BACKPORT_DISABLED_KCONFIG_OPTION')
519 new
.append('!BACKPORT_KERNEL_%s' % dep
.replace('.', '_'))
521 configtree
.add_dependencies(deplist
)
522 git_debug_snapshot(args
, "add kernel version dependencies")
524 # disable things in makefiles that can't be selected and that the
525 # build shouldn't recurse into because they don't exist -- if we
526 # don't do that then a symbol from the kernel could cause the build
527 # to attempt to recurse and fail
529 # Note that we split the regex after 50 symbols, this is because of a
530 # limitation in the regex implementation (it only supports 100 nested
531 # groups -- 50 seemed safer and is still fast)
533 for some_symbols
in [disable_makefile
[i
:i
+ 50] for i
in range(0, len(disable_makefile
), 50)]:
534 r
= '^([^#].*((CPTCFG|CONFIG)_(' + '|'.join([s
for s
in some_symbols
]) + ')))'
535 regexes
.append(re
.compile(r
, re
.MULTILINE
))
536 for f
in maketree
.get_makefiles():
537 data
= open(f
, 'r').read()
539 data
= r
.sub(r
'#\1', data
)
543 git_debug_snapshot(args
, "disable unsatisfied Makefile parts")
548 if __name__
== '__main__':