xref: /OK3568_Linux_fs/u-boot/tools/buildman/control.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Copyright (c) 2013 The Chromium OS Authors.
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# SPDX-License-Identifier:	GPL-2.0+
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun
6*4882a593Smuzhiyunimport multiprocessing
7*4882a593Smuzhiyunimport os
8*4882a593Smuzhiyunimport shutil
9*4882a593Smuzhiyunimport sys
10*4882a593Smuzhiyun
11*4882a593Smuzhiyunimport board
12*4882a593Smuzhiyunimport bsettings
13*4882a593Smuzhiyunfrom builder import Builder
14*4882a593Smuzhiyunimport gitutil
15*4882a593Smuzhiyunimport patchstream
16*4882a593Smuzhiyunimport terminal
17*4882a593Smuzhiyunfrom terminal import Print
18*4882a593Smuzhiyunimport toolchain
19*4882a593Smuzhiyunimport command
20*4882a593Smuzhiyunimport subprocess
21*4882a593Smuzhiyun
22*4882a593Smuzhiyundef GetPlural(count):
23*4882a593Smuzhiyun    """Returns a plural 's' if count is not 1"""
24*4882a593Smuzhiyun    return 's' if count != 1 else ''
25*4882a593Smuzhiyun
26*4882a593Smuzhiyundef GetActionSummary(is_summary, commits, selected, options):
27*4882a593Smuzhiyun    """Return a string summarising the intended action.
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun    Returns:
30*4882a593Smuzhiyun        Summary string.
31*4882a593Smuzhiyun    """
32*4882a593Smuzhiyun    if commits:
33*4882a593Smuzhiyun        count = len(commits)
34*4882a593Smuzhiyun        count = (count + options.step - 1) / options.step
35*4882a593Smuzhiyun        commit_str = '%d commit%s' % (count, GetPlural(count))
36*4882a593Smuzhiyun    else:
37*4882a593Smuzhiyun        commit_str = 'current source'
38*4882a593Smuzhiyun    str = '%s %s for %d boards' % (
39*4882a593Smuzhiyun        'Summary of' if is_summary else 'Building', commit_str,
40*4882a593Smuzhiyun        len(selected))
41*4882a593Smuzhiyun    str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
42*4882a593Smuzhiyun            GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
43*4882a593Smuzhiyun    return str
44*4882a593Smuzhiyun
45*4882a593Smuzhiyundef ShowActions(series, why_selected, boards_selected, builder, options):
46*4882a593Smuzhiyun    """Display a list of actions that we would take, if not a dry run.
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun    Args:
49*4882a593Smuzhiyun        series: Series object
50*4882a593Smuzhiyun        why_selected: Dictionary where each key is a buildman argument
51*4882a593Smuzhiyun                provided by the user, and the value is the list of boards
52*4882a593Smuzhiyun                brought in by that argument. For example, 'arm' might bring
53*4882a593Smuzhiyun                in 400 boards, so in this case the key would be 'arm' and
54*4882a593Smuzhiyun                the value would be a list of board names.
55*4882a593Smuzhiyun        boards_selected: Dict of selected boards, key is target name,
56*4882a593Smuzhiyun                value is Board object
57*4882a593Smuzhiyun        builder: The builder that will be used to build the commits
58*4882a593Smuzhiyun        options: Command line options object
59*4882a593Smuzhiyun    """
60*4882a593Smuzhiyun    col = terminal.Color()
61*4882a593Smuzhiyun    print 'Dry run, so not doing much. But I would do this:'
62*4882a593Smuzhiyun    print
63*4882a593Smuzhiyun    if series:
64*4882a593Smuzhiyun        commits = series.commits
65*4882a593Smuzhiyun    else:
66*4882a593Smuzhiyun        commits = None
67*4882a593Smuzhiyun    print GetActionSummary(False, commits, boards_selected,
68*4882a593Smuzhiyun            options)
69*4882a593Smuzhiyun    print 'Build directory: %s' % builder.base_dir
70*4882a593Smuzhiyun    if commits:
71*4882a593Smuzhiyun        for upto in range(0, len(series.commits), options.step):
72*4882a593Smuzhiyun            commit = series.commits[upto]
73*4882a593Smuzhiyun            print '   ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
74*4882a593Smuzhiyun            print commit.subject
75*4882a593Smuzhiyun    print
76*4882a593Smuzhiyun    for arg in why_selected:
77*4882a593Smuzhiyun        if arg != 'all':
78*4882a593Smuzhiyun            print arg, ': %d boards' % len(why_selected[arg])
79*4882a593Smuzhiyun            if options.verbose:
80*4882a593Smuzhiyun                print '   %s' % ' '.join(why_selected[arg])
81*4882a593Smuzhiyun    print ('Total boards to build for each commit: %d\n' %
82*4882a593Smuzhiyun            len(why_selected['all']))
83*4882a593Smuzhiyun
84*4882a593Smuzhiyundef DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
85*4882a593Smuzhiyun               clean_dir=False):
86*4882a593Smuzhiyun    """The main control code for buildman
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun    Args:
89*4882a593Smuzhiyun        options: Command line options object
90*4882a593Smuzhiyun        args: Command line arguments (list of strings)
91*4882a593Smuzhiyun        toolchains: Toolchains to use - this should be a Toolchains()
92*4882a593Smuzhiyun                object. If None, then it will be created and scanned
93*4882a593Smuzhiyun        make_func: Make function to use for the builder. This is called
94*4882a593Smuzhiyun                to execute 'make'. If this is None, the normal function
95*4882a593Smuzhiyun                will be used, which calls the 'make' tool with suitable
96*4882a593Smuzhiyun                arguments. This setting is useful for tests.
97*4882a593Smuzhiyun        board: Boards() object to use, containing a list of available
98*4882a593Smuzhiyun                boards. If this is None it will be created and scanned.
99*4882a593Smuzhiyun    """
100*4882a593Smuzhiyun    global builder
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun    if options.full_help:
103*4882a593Smuzhiyun        pager = os.getenv('PAGER')
104*4882a593Smuzhiyun        if not pager:
105*4882a593Smuzhiyun            pager = 'more'
106*4882a593Smuzhiyun        fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
107*4882a593Smuzhiyun                             'README')
108*4882a593Smuzhiyun        command.Run(pager, fname)
109*4882a593Smuzhiyun        return 0
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun    gitutil.Setup()
112*4882a593Smuzhiyun    col = terminal.Color()
113*4882a593Smuzhiyun
114*4882a593Smuzhiyun    options.git_dir = os.path.join(options.git, '.git')
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun    no_toolchains = toolchains is None
117*4882a593Smuzhiyun    if no_toolchains:
118*4882a593Smuzhiyun        toolchains = toolchain.Toolchains()
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun    if options.fetch_arch:
121*4882a593Smuzhiyun        if options.fetch_arch == 'list':
122*4882a593Smuzhiyun            sorted_list = toolchains.ListArchs()
123*4882a593Smuzhiyun            print col.Color(col.BLUE, 'Available architectures: %s\n' %
124*4882a593Smuzhiyun                            ' '.join(sorted_list))
125*4882a593Smuzhiyun            return 0
126*4882a593Smuzhiyun        else:
127*4882a593Smuzhiyun            fetch_arch = options.fetch_arch
128*4882a593Smuzhiyun            if fetch_arch == 'all':
129*4882a593Smuzhiyun                fetch_arch = ','.join(toolchains.ListArchs())
130*4882a593Smuzhiyun                print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
131*4882a593Smuzhiyun                                fetch_arch)
132*4882a593Smuzhiyun            for arch in fetch_arch.split(','):
133*4882a593Smuzhiyun                print
134*4882a593Smuzhiyun                ret = toolchains.FetchAndInstall(arch)
135*4882a593Smuzhiyun                if ret:
136*4882a593Smuzhiyun                    return ret
137*4882a593Smuzhiyun            return 0
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun    if no_toolchains:
140*4882a593Smuzhiyun        toolchains.GetSettings()
141*4882a593Smuzhiyun        toolchains.Scan(options.list_tool_chains)
142*4882a593Smuzhiyun    if options.list_tool_chains:
143*4882a593Smuzhiyun        toolchains.List()
144*4882a593Smuzhiyun        print
145*4882a593Smuzhiyun        return 0
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun    # Work out how many commits to build. We want to build everything on the
148*4882a593Smuzhiyun    # branch. We also build the upstream commit as a control so we can see
149*4882a593Smuzhiyun    # problems introduced by the first commit on the branch.
150*4882a593Smuzhiyun    count = options.count
151*4882a593Smuzhiyun    has_range = options.branch and '..' in options.branch
152*4882a593Smuzhiyun    if count == -1:
153*4882a593Smuzhiyun        if not options.branch:
154*4882a593Smuzhiyun            count = 1
155*4882a593Smuzhiyun        else:
156*4882a593Smuzhiyun            if has_range:
157*4882a593Smuzhiyun                count, msg = gitutil.CountCommitsInRange(options.git_dir,
158*4882a593Smuzhiyun                                                         options.branch)
159*4882a593Smuzhiyun            else:
160*4882a593Smuzhiyun                count, msg = gitutil.CountCommitsInBranch(options.git_dir,
161*4882a593Smuzhiyun                                                          options.branch)
162*4882a593Smuzhiyun            if count is None:
163*4882a593Smuzhiyun                sys.exit(col.Color(col.RED, msg))
164*4882a593Smuzhiyun            elif count == 0:
165*4882a593Smuzhiyun                sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
166*4882a593Smuzhiyun                                   options.branch))
167*4882a593Smuzhiyun            if msg:
168*4882a593Smuzhiyun                print col.Color(col.YELLOW, msg)
169*4882a593Smuzhiyun            count += 1   # Build upstream commit also
170*4882a593Smuzhiyun
171*4882a593Smuzhiyun    if not count:
172*4882a593Smuzhiyun        str = ("No commits found to process in branch '%s': "
173*4882a593Smuzhiyun               "set branch's upstream or use -c flag" % options.branch)
174*4882a593Smuzhiyun        sys.exit(col.Color(col.RED, str))
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun    # Work out what subset of the boards we are building
177*4882a593Smuzhiyun    if not boards:
178*4882a593Smuzhiyun        board_file = os.path.join(options.git, 'boards.cfg')
179*4882a593Smuzhiyun        status = subprocess.call([os.path.join(options.git,
180*4882a593Smuzhiyun                                                'tools/genboardscfg.py')])
181*4882a593Smuzhiyun        if status != 0:
182*4882a593Smuzhiyun                sys.exit("Failed to generate boards.cfg")
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun        boards = board.Boards()
185*4882a593Smuzhiyun        boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
186*4882a593Smuzhiyun
187*4882a593Smuzhiyun    exclude = []
188*4882a593Smuzhiyun    if options.exclude:
189*4882a593Smuzhiyun        for arg in options.exclude:
190*4882a593Smuzhiyun            exclude += arg.split(',')
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun    why_selected = boards.SelectBoards(args, exclude)
193*4882a593Smuzhiyun    selected = boards.GetSelected()
194*4882a593Smuzhiyun    if not len(selected):
195*4882a593Smuzhiyun        sys.exit(col.Color(col.RED, 'No matching boards found'))
196*4882a593Smuzhiyun
197*4882a593Smuzhiyun    # Read the metadata from the commits. First look at the upstream commit,
198*4882a593Smuzhiyun    # then the ones in the branch. We would like to do something like
199*4882a593Smuzhiyun    # upstream/master~..branch but that isn't possible if upstream/master is
200*4882a593Smuzhiyun    # a merge commit (it will list all the commits that form part of the
201*4882a593Smuzhiyun    # merge)
202*4882a593Smuzhiyun    # Conflicting tags are not a problem for buildman, since it does not use
203*4882a593Smuzhiyun    # them. For example, Series-version is not useful for buildman. On the
204*4882a593Smuzhiyun    # other hand conflicting tags will cause an error. So allow later tags
205*4882a593Smuzhiyun    # to overwrite earlier ones by setting allow_overwrite=True
206*4882a593Smuzhiyun    if options.branch:
207*4882a593Smuzhiyun        if count == -1:
208*4882a593Smuzhiyun            if has_range:
209*4882a593Smuzhiyun                range_expr = options.branch
210*4882a593Smuzhiyun            else:
211*4882a593Smuzhiyun                range_expr = gitutil.GetRangeInBranch(options.git_dir,
212*4882a593Smuzhiyun                                                      options.branch)
213*4882a593Smuzhiyun            upstream_commit = gitutil.GetUpstream(options.git_dir,
214*4882a593Smuzhiyun                                                  options.branch)
215*4882a593Smuzhiyun            series = patchstream.GetMetaDataForList(upstream_commit,
216*4882a593Smuzhiyun                options.git_dir, 1, series=None, allow_overwrite=True)
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun            series = patchstream.GetMetaDataForList(range_expr,
219*4882a593Smuzhiyun                    options.git_dir, None, series, allow_overwrite=True)
220*4882a593Smuzhiyun        else:
221*4882a593Smuzhiyun            # Honour the count
222*4882a593Smuzhiyun            series = patchstream.GetMetaDataForList(options.branch,
223*4882a593Smuzhiyun                    options.git_dir, count, series=None, allow_overwrite=True)
224*4882a593Smuzhiyun    else:
225*4882a593Smuzhiyun        series = None
226*4882a593Smuzhiyun        if not options.dry_run:
227*4882a593Smuzhiyun            options.verbose = True
228*4882a593Smuzhiyun            if not options.summary:
229*4882a593Smuzhiyun                options.show_errors = True
230*4882a593Smuzhiyun
231*4882a593Smuzhiyun    # By default we have one thread per CPU. But if there are not enough jobs
232*4882a593Smuzhiyun    # we can have fewer threads and use a high '-j' value for make.
233*4882a593Smuzhiyun    if not options.threads:
234*4882a593Smuzhiyun        options.threads = min(multiprocessing.cpu_count(), len(selected))
235*4882a593Smuzhiyun    if not options.jobs:
236*4882a593Smuzhiyun        options.jobs = max(1, (multiprocessing.cpu_count() +
237*4882a593Smuzhiyun                len(selected) - 1) / len(selected))
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun    if not options.step:
240*4882a593Smuzhiyun        options.step = len(series.commits) - 1
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun    gnu_make = command.Output(os.path.join(options.git,
243*4882a593Smuzhiyun            'scripts/show-gnu-make'), raise_on_error=False).rstrip()
244*4882a593Smuzhiyun    if not gnu_make:
245*4882a593Smuzhiyun        sys.exit('GNU Make not found')
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun    # Create a new builder with the selected options.
248*4882a593Smuzhiyun    output_dir = options.output_dir
249*4882a593Smuzhiyun    if options.branch:
250*4882a593Smuzhiyun        dirname = options.branch.replace('/', '_')
251*4882a593Smuzhiyun        # As a special case allow the board directory to be placed in the
252*4882a593Smuzhiyun        # output directory itself rather than any subdirectory.
253*4882a593Smuzhiyun        if not options.no_subdirs:
254*4882a593Smuzhiyun            output_dir = os.path.join(options.output_dir, dirname)
255*4882a593Smuzhiyun    if (clean_dir and output_dir != options.output_dir and
256*4882a593Smuzhiyun            os.path.exists(output_dir)):
257*4882a593Smuzhiyun        shutil.rmtree(output_dir)
258*4882a593Smuzhiyun    builder = Builder(toolchains, output_dir, options.git_dir,
259*4882a593Smuzhiyun            options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
260*4882a593Smuzhiyun            show_unknown=options.show_unknown, step=options.step,
261*4882a593Smuzhiyun            no_subdirs=options.no_subdirs, full_path=options.full_path,
262*4882a593Smuzhiyun            verbose_build=options.verbose_build,
263*4882a593Smuzhiyun            incremental=options.incremental,
264*4882a593Smuzhiyun            per_board_out_dir=options.per_board_out_dir,
265*4882a593Smuzhiyun            config_only=options.config_only,
266*4882a593Smuzhiyun            squash_config_y=not options.preserve_config_y)
267*4882a593Smuzhiyun    builder.force_config_on_failure = not options.quick
268*4882a593Smuzhiyun    if make_func:
269*4882a593Smuzhiyun        builder.do_make = make_func
270*4882a593Smuzhiyun
271*4882a593Smuzhiyun    # For a dry run, just show our actions as a sanity check
272*4882a593Smuzhiyun    if options.dry_run:
273*4882a593Smuzhiyun        ShowActions(series, why_selected, selected, builder, options)
274*4882a593Smuzhiyun    else:
275*4882a593Smuzhiyun        builder.force_build = options.force_build
276*4882a593Smuzhiyun        builder.force_build_failures = options.force_build_failures
277*4882a593Smuzhiyun        builder.force_reconfig = options.force_reconfig
278*4882a593Smuzhiyun        builder.in_tree = options.in_tree
279*4882a593Smuzhiyun
280*4882a593Smuzhiyun        # Work out which boards to build
281*4882a593Smuzhiyun        board_selected = boards.GetSelectedDict()
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun        if series:
284*4882a593Smuzhiyun            commits = series.commits
285*4882a593Smuzhiyun            # Number the commits for test purposes
286*4882a593Smuzhiyun            for commit in range(len(commits)):
287*4882a593Smuzhiyun                commits[commit].sequence = commit
288*4882a593Smuzhiyun        else:
289*4882a593Smuzhiyun            commits = None
290*4882a593Smuzhiyun
291*4882a593Smuzhiyun        Print(GetActionSummary(options.summary, commits, board_selected,
292*4882a593Smuzhiyun                                options))
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun        # We can't show function sizes without board details at present
295*4882a593Smuzhiyun        if options.show_bloat:
296*4882a593Smuzhiyun            options.show_detail = True
297*4882a593Smuzhiyun        builder.SetDisplayOptions(options.show_errors, options.show_sizes,
298*4882a593Smuzhiyun                                  options.show_detail, options.show_bloat,
299*4882a593Smuzhiyun                                  options.list_error_boards,
300*4882a593Smuzhiyun                                  options.show_config)
301*4882a593Smuzhiyun        if options.summary:
302*4882a593Smuzhiyun            builder.ShowSummary(commits, board_selected)
303*4882a593Smuzhiyun        else:
304*4882a593Smuzhiyun            fail, warned = builder.BuildBoards(commits, board_selected,
305*4882a593Smuzhiyun                                options.keep_outputs, options.verbose)
306*4882a593Smuzhiyun            if fail:
307*4882a593Smuzhiyun                return 128
308*4882a593Smuzhiyun            elif warned:
309*4882a593Smuzhiyun                return 129
310*4882a593Smuzhiyun    return 0
311