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(sys
.argv
[0]))
10 sys
.path
.append(os
.path
.join(source_dir
, 'lib'))
11 # and import libraries we have
12 import kconfig
, git
, patch
, make
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 (not i
[-1] in ('c', 'h') and
142 not i
in ('Kconfig', 'Makefile') and
143 not os
.path
.isdir(os
.path
.join(dir, i
))):
146 copytree(os
.path
.join(srcpath
, srcitem
),
147 os
.path
.join(outdir
, tgtitem
),
151 os
.makedirs(os
.path
.join(outdir
, os
.path
.dirname(tgtitem
)))
153 # ignore dirs we might have created just now
154 if e
.errno
!= errno
.EEXIST
:
156 shutil
.copy(os
.path
.join(srcpath
, srcitem
),
157 os
.path
.join(outdir
, tgtitem
))
160 def copy_git_files(srcpath
, copy_list
, rev
, outdir
):
162 "Copy" files from a git repository. This really means listing them with
163 ls-tree and then using git show to obtain all the blobs.
165 for srcitem
, tgtitem
in copy_list
:
166 for m
, t
, h
, f
in git
.ls_tree(rev
=rev
, files
=(srcitem
,), tree
=srcpath
):
168 f
= os
.path
.join(outdir
, f
)
169 d
= os
.path
.dirname(f
)
170 if not os
.path
.exists(d
):
173 git
.get_blob(h
, outf
, tree
=srcpath
)
175 os
.chmod(f
, int(m
, 8))
178 def git_debug_init(args
):
180 Initialize a git repository in the output directory and commit the current
181 code in it. This is only used for debugging the transformations this code
182 will do to the output later.
184 if not args
.gitdebug
:
186 git
.init(tree
=args
.outdir
)
187 git
.commit_all("Copied code", tree
=args
.outdir
)
190 def git_debug_snapshot(args
, name
):
192 Take a git snapshot for the debugging.
194 if not args
.gitdebug
:
196 git
.commit_all(name
, tree
=args
.outdir
)
200 # set up and parse arguments
201 parser
= argparse
.ArgumentParser(description
='generate backport tree')
202 parser
.add_argument('kerneldir', metavar
='<kernel tree>', type=str,
203 help='Kernel tree to copy drivers from')
204 parser
.add_argument('outdir', metavar
='<output directory>', type=str,
205 help='Directory to write the generated tree to')
206 parser
.add_argument('--copy-list', metavar
='<listfile>', type=argparse
.FileType('r'),
208 help='File containing list of files/directories to copy, default "copy-list"')
209 parser
.add_argument('--git-revision', metavar
='<revision>', type=str,
210 help='git commit revision (see gitrevisions(7)) to take objects from.' +
211 'If this is specified, the kernel tree is used as git object storage ' +
212 'and we use git ls-tree to get the files.')
213 parser
.add_argument('--clean', const
=True, default
=False, action
="store_const",
214 help='Clean output directory instead of erroring if it isn\'t empty')
215 parser
.add_argument('--refresh', const
=True, default
=False, action
="store_const",
216 help='Refresh patches as they are applied, the source dir will be modified!')
217 parser
.add_argument('--base-name', metavar
='<name>', type=str, default
='Linux',
218 help='name of base tree, default just "Linux"')
219 parser
.add_argument('--gitdebug', const
=True, default
=False, action
="store_const",
220 help='Use git, in the output tree, to debug the various transformation steps ' +
221 'that the tree generation makes (apply patches, ...)')
222 parser
.add_argument('--verbose', const
=True, default
=False, action
="store_const",
223 help='Print more verbose information')
224 parser
.add_argument('--extra-driver', nargs
=2, metavar
=('<source dir>', '<copy-list>'), type=str,
225 action
='append', default
=[], help='Extra driver directory/copy-list.')
226 args
= parser
.parse_args()
228 # start processing ...
230 copy_list
= read_copy_list(args
.copy_list
)
231 deplist
= read_dependencies(os
.path
.join(source_dir
, 'dependencies'))
233 # validate output directory
234 check_output_dir(args
.outdir
, args
.clean
)
237 backport_files
= [(x
, x
) for x
in [
238 'Kconfig', 'Makefile', 'Makefile.build', 'Makefile.kernel',
239 'Makefile.real', 'compat/', 'include/', 'kconfig/',
241 if not args
.git_revision
:
242 print 'Copy original source files ...'
243 copy_files(os
.path
.join(source_dir
, 'backport'), backport_files
, args
.outdir
)
244 copy_files(args
.kerneldir
, copy_list
, args
.outdir
)
246 print 'Get original source files from git ...'
247 copy_files(os
.path
.join(source_dir
, 'backport'), backport_files
, args
.outdir
)
248 copy_git_files(args
.kerneldir
, copy_list
, args
.git_revision
, args
.outdir
)
250 # FIXME: should we add a git version of this (e.g. --git-extra-driver)?
251 for src
, copy_list
in args
.extra_driver
:
252 copy_files(src
, read_copy_list(open(copy_list
, 'r')), args
.outdir
)
256 print 'Apply patches ...'
258 for root
, dirs
, files
in os
.walk(os
.path
.join(source_dir
, 'patches')):
260 patchdirs
.append(root
)
262 for pdir
in patchdirs
:
266 # FIXME: again, use .gitignore?
269 pfile
= os
.path
.join(pdir
, pfile
)
270 # read the patch file
271 p
= patch
.fromfile(pfile
)
275 # check if the first file the patch touches exists, if so
276 # assume the patch needs to be applied -- otherwise continue
277 patched_file
= '/'.join(p
.items
[0].source
.split('/')[1:])
278 fullfn
= os
.path
.join(args
.outdir
, patched_file
)
279 if not os
.path
.exists(fullfn
):
283 print "Applying changes from", os
.path
.basename(pdir
)
286 # but for refresh, of course look at all files the patch touches
287 for patchitem
in p
.items
:
288 patched_file
= '/'.join(patchitem
.source
.split('/')[1:])
289 fullfn
= os
.path
.join(args
.outdir
, patched_file
)
290 shutil
.copyfile(fullfn
, fullfn
+ '.orig_file')
292 process
= subprocess
.Popen(['patch', '-p1'], stdout
=subprocess
.PIPE
,
293 stderr
=subprocess
.STDOUT
, stdin
=subprocess
.PIPE
,
294 close_fds
=True, universal_newlines
=True,
296 output
= process
.communicate(input=open(pfile
, 'r').read())[0]
297 output
= output
.split('\n')
303 if process
.returncode
!= 0:
305 print "Failed to apply changes from", os
.path
.basename(pdir
)
311 pfilef
= open(pfile
+ '.tmp', 'w')
312 for patchitem
in p
.items
:
313 patched_file
= '/'.join(patchitem
.source
.split('/')[1:])
314 fullfn
= os
.path
.join(args
.outdir
, patched_file
)
315 process
= subprocess
.Popen(['diff', '-u', patched_file
+ '.orig_file', patched_file
,
316 '--label', 'a/' + patched_file
,
317 '--label', 'b/' + patched_file
],
318 stdout
=pfilef
, close_fds
=True,
319 universal_newlines
=True, cwd
=args
.outdir
)
321 os
.unlink(fullfn
+ '.orig_file')
322 if not process
.returncode
in (0, 1):
323 print "Diffing for refresh failed!"
325 os
.unlink(pfile
+ '.tmp')
328 os
.rename(pfile
+ '.tmp', pfile
)
330 # remove orig/rej files that patch sometimes creates
331 for root
, dirs
, files
in os
.walk(args
.outdir
):
333 if f
[-5:] == '.orig' or f
[-4:] == '.rej':
334 os
.unlink(os
.path
.join(root
, f
))
337 print "Not applying changes from %s, not needed" % (os
.path
.basename(pdir
),)
339 git_debug_snapshot(args
, "apply backport patches from %s" % (os
.path
.basename(pdir
),))
341 # some post-processing is required
342 configtree
= kconfig
.ConfigTree(os
.path
.join(args
.outdir
, 'Kconfig'))
343 print 'Modify Kconfig tree ...'
344 configtree
.prune_sources(ignore
=['Kconfig.kernel', 'Kconfig.versions'])
345 git_debug_snapshot(args
, "prune Kconfig tree")
346 configtree
.force_tristate_modular()
347 git_debug_snapshot(args
, "force tristate options modular")
348 configtree
.modify_selects()
349 git_debug_snapshot(args
, "convert select to depends on")
351 # write the versioning file
352 backports_version
= git
.describe(tree
=source_dir
)
353 kernel_version
= git
.describe(tree
=args
.kerneldir
)
354 f
= open(os
.path
.join(args
.outdir
, 'versions'), 'w')
355 f
.write('BACKPORTS_VERSION="%s"\n' % backports_version
)
356 f
.write('KERNEL_VERSION="%s"\n' % kernel_version
)
357 f
.write('KERNEL_NAME="%s"\n' % args
.base_name
)
360 symbols
= configtree
.symbols()
362 # write local symbol list -- needed during build
363 f
= open(os
.path
.join(args
.outdir
, '.local-symbols'), 'w')
365 f
.write('%s=\n' % sym
)
368 git_debug_snapshot(args
, "add versions/symbols files")
370 print 'Rewrite Makefiles and Kconfig files ...'
372 # rewrite Makefile and source symbols
374 for some_symbols
in [symbols
[i
:i
+ 50] for i
in range(0, len(symbols
), 50)]:
375 r
= 'CONFIG_((' + '|'.join([s
+ '(_MODULE)?' for s
in some_symbols
]) + ')([^A-Za-z0-9_]|$))'
376 regexes
.append(re
.compile(r
, re
.MULTILINE
))
377 for root
, dirs
, files
in os
.walk(args
.outdir
):
378 # don't go into .git dir (possible debug thing)
382 data
= open(os
.path
.join(root
, f
), 'r').read()
384 data
= r
.sub(r
'CPTCFG_\1', data
)
385 fo
= open(os
.path
.join(root
, f
), 'w')
389 git_debug_snapshot(args
, "rename config symbol usage")
391 # disable unbuildable Kconfig symbols and stuff Makefiles that doesn't exist
392 maketree
= make
.MakeTree(os
.path
.join(args
.outdir
, 'Makefile.kernel'))
394 disable_makefile
= []
395 for sym
in maketree
.get_impossible_symbols():
396 if sym
[:7] == 'CPTCFG_':
397 disable_kconfig
.append(sym
[7:])
399 disable_makefile
.append(sym
[7:])
401 configtree
.disable_symbols(disable_kconfig
)
402 git_debug_snapshot(args
, "disable impossible kconfig symbols")
404 # add kernel version dependencies to Kconfig, from the dependency list
406 for sym
in tuple(deplist
.keys()):
408 for dep
in deplist
[sym
]:
409 new
.append('!BACKPORT_KERNEL_%s' % dep
.replace('.', '_'))
411 configtree
.add_dependencies(deplist
)
412 git_debug_snapshot(args
, "add kernel version dependencies")
414 # disable things in makefiles that can't be selected and that the
415 # build shouldn't recurse into because they don't exist -- if we
416 # don't do that then a symbol from the kernel could cause the build
417 # to attempt to recurse and fail
419 # Note that we split the regex after 50 symbols, this is because of a
420 # limitation in the regex implementation (it only supports 100 nested
421 # groups -- 50 seemed safer and is still fast)
423 for some_symbols
in [disable_makefile
[i
:i
+ 50] for i
in range(0, len(disable_makefile
), 50)]:
424 r
= '(CONFIG_(' + '|'.join([s
for s
in some_symbols
]) + '))'
425 regexes
.append(re
.compile(r
, re
.MULTILINE
))
426 for f
in maketree
.get_makefiles():
427 data
= open(f
, 'r').read()
429 data
= r
.sub(r
'IMPOSSIBLE_\2', data
)
433 git_debug_snapshot(args
, "disable unsatisfied Makefile parts")