xref: /rk3399_rockchip-uboot/tools/buildman/control.py (revision 5abab20dfb20406137e8b7d659aee3cf43dff351)
1fc3fe1c2SSimon Glass# Copyright (c) 2013 The Chromium OS Authors.
2fc3fe1c2SSimon Glass#
31a459660SWolfgang Denk# SPDX-License-Identifier:	GPL-2.0+
4fc3fe1c2SSimon Glass#
5fc3fe1c2SSimon Glass
6fc3fe1c2SSimon Glassimport multiprocessing
7fc3fe1c2SSimon Glassimport os
8883a321aSSimon Glassimport shutil
9fc3fe1c2SSimon Glassimport sys
10fc3fe1c2SSimon Glass
11fc3fe1c2SSimon Glassimport board
12fc3fe1c2SSimon Glassimport bsettings
13fc3fe1c2SSimon Glassfrom builder import Builder
14fc3fe1c2SSimon Glassimport gitutil
15fc3fe1c2SSimon Glassimport patchstream
16fc3fe1c2SSimon Glassimport terminal
17d4144e45SSimon Glassfrom terminal import Print
18fc3fe1c2SSimon Glassimport toolchain
1999796923SMasahiro Yamadaimport command
2073f30b9bSMasahiro Yamadaimport subprocess
21fc3fe1c2SSimon Glass
22fc3fe1c2SSimon Glassdef GetPlural(count):
23fc3fe1c2SSimon Glass    """Returns a plural 's' if count is not 1"""
24fc3fe1c2SSimon Glass    return 's' if count != 1 else ''
25fc3fe1c2SSimon Glass
26fea5858eSSimon Glassdef GetActionSummary(is_summary, commits, selected, options):
27fc3fe1c2SSimon Glass    """Return a string summarising the intended action.
28fc3fe1c2SSimon Glass
29fc3fe1c2SSimon Glass    Returns:
30fc3fe1c2SSimon Glass        Summary string.
31fc3fe1c2SSimon Glass    """
32fea5858eSSimon Glass    if commits:
33fea5858eSSimon Glass        count = len(commits)
34fc3fe1c2SSimon Glass        count = (count + options.step - 1) / options.step
35fea5858eSSimon Glass        commit_str = '%d commit%s' % (count, GetPlural(count))
36fea5858eSSimon Glass    else:
37fea5858eSSimon Glass        commit_str = 'current source'
38fea5858eSSimon Glass    str = '%s %s for %d boards' % (
39fea5858eSSimon Glass        'Summary of' if is_summary else 'Building', commit_str,
40fc3fe1c2SSimon Glass        len(selected))
41fc3fe1c2SSimon Glass    str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
42fc3fe1c2SSimon Glass            GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
43fc3fe1c2SSimon Glass    return str
44fc3fe1c2SSimon Glass
45fc3fe1c2SSimon Glassdef ShowActions(series, why_selected, boards_selected, builder, options):
46fc3fe1c2SSimon Glass    """Display a list of actions that we would take, if not a dry run.
47fc3fe1c2SSimon Glass
48fc3fe1c2SSimon Glass    Args:
49fc3fe1c2SSimon Glass        series: Series object
50fc3fe1c2SSimon Glass        why_selected: Dictionary where each key is a buildman argument
51fc3fe1c2SSimon Glass                provided by the user, and the value is the boards brought
52fc3fe1c2SSimon Glass                in by that argument. For example, 'arm' might bring in
53fc3fe1c2SSimon Glass                400 boards, so in this case the key would be 'arm' and
54fc3fe1c2SSimon Glass                the value would be a list of board names.
55fc3fe1c2SSimon Glass        boards_selected: Dict of selected boards, key is target name,
56fc3fe1c2SSimon Glass                value is Board object
57fc3fe1c2SSimon Glass        builder: The builder that will be used to build the commits
58fc3fe1c2SSimon Glass        options: Command line options object
59fc3fe1c2SSimon Glass    """
60fc3fe1c2SSimon Glass    col = terminal.Color()
61fc3fe1c2SSimon Glass    print 'Dry run, so not doing much. But I would do this:'
62fc3fe1c2SSimon Glass    print
63fea5858eSSimon Glass    if series:
64fea5858eSSimon Glass        commits = series.commits
65fea5858eSSimon Glass    else:
66fea5858eSSimon Glass        commits = None
67fea5858eSSimon Glass    print GetActionSummary(False, commits, boards_selected,
68fc3fe1c2SSimon Glass            options)
69fc3fe1c2SSimon Glass    print 'Build directory: %s' % builder.base_dir
70fea5858eSSimon Glass    if commits:
71fc3fe1c2SSimon Glass        for upto in range(0, len(series.commits), options.step):
72fc3fe1c2SSimon Glass            commit = series.commits[upto]
731ddda1b3SSimon Glass            print '   ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
74fc3fe1c2SSimon Glass            print commit.subject
75fc3fe1c2SSimon Glass    print
76fc3fe1c2SSimon Glass    for arg in why_selected:
77fc3fe1c2SSimon Glass        if arg != 'all':
78fc3fe1c2SSimon Glass            print arg, ': %d boards' % why_selected[arg]
79fc3fe1c2SSimon Glass    print ('Total boards to build for each commit: %d\n' %
80fc3fe1c2SSimon Glass            why_selected['all'])
81fc3fe1c2SSimon Glass
82883a321aSSimon Glassdef DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
83883a321aSSimon Glass               clean_dir=False):
84fc3fe1c2SSimon Glass    """The main control code for buildman
85fc3fe1c2SSimon Glass
86fc3fe1c2SSimon Glass    Args:
87fc3fe1c2SSimon Glass        options: Command line options object
88fc3fe1c2SSimon Glass        args: Command line arguments (list of strings)
89d4144e45SSimon Glass        toolchains: Toolchains to use - this should be a Toolchains()
90d4144e45SSimon Glass                object. If None, then it will be created and scanned
91d4144e45SSimon Glass        make_func: Make function to use for the builder. This is called
92d4144e45SSimon Glass                to execute 'make'. If this is None, the normal function
93d4144e45SSimon Glass                will be used, which calls the 'make' tool with suitable
94d4144e45SSimon Glass                arguments. This setting is useful for tests.
95823e60b6SSimon Glass        board: Boards() object to use, containing a list of available
96823e60b6SSimon Glass                boards. If this is None it will be created and scanned.
97fc3fe1c2SSimon Glass    """
98883a321aSSimon Glass    global builder
99883a321aSSimon Glass
10048ba5856SSimon Glass    if options.full_help:
10148ba5856SSimon Glass        pager = os.getenv('PAGER')
10248ba5856SSimon Glass        if not pager:
10348ba5856SSimon Glass            pager = 'more'
10448ba5856SSimon Glass        fname = os.path.join(os.path.dirname(sys.argv[0]), 'README')
10548ba5856SSimon Glass        command.Run(pager, fname)
10648ba5856SSimon Glass        return 0
10748ba5856SSimon Glass
108fc3fe1c2SSimon Glass    gitutil.Setup()
109fc3fe1c2SSimon Glass
110fc3fe1c2SSimon Glass    options.git_dir = os.path.join(options.git, '.git')
111fc3fe1c2SSimon Glass
112d4144e45SSimon Glass    if not toolchains:
113fc3fe1c2SSimon Glass        toolchains = toolchain.Toolchains()
114d4144e45SSimon Glass        toolchains.GetSettings()
115fc3fe1c2SSimon Glass        toolchains.Scan(options.list_tool_chains)
116fc3fe1c2SSimon Glass    if options.list_tool_chains:
117fc3fe1c2SSimon Glass        toolchains.List()
118fc3fe1c2SSimon Glass        print
1192c3deb97SSimon Glass        return 0
120fc3fe1c2SSimon Glass
121fc3fe1c2SSimon Glass    # Work out how many commits to build. We want to build everything on the
122fc3fe1c2SSimon Glass    # branch. We also build the upstream commit as a control so we can see
123fc3fe1c2SSimon Glass    # problems introduced by the first commit on the branch.
124fc3fe1c2SSimon Glass    col = terminal.Color()
125fc3fe1c2SSimon Glass    count = options.count
126*5abab20dSSimon Glass    has_range = options.branch and '..' in options.branch
127fc3fe1c2SSimon Glass    if count == -1:
128fc3fe1c2SSimon Glass        if not options.branch:
129fea5858eSSimon Glass            count = 1
130fea5858eSSimon Glass        else:
131*5abab20dSSimon Glass            if has_range:
132*5abab20dSSimon Glass                count, msg = gitutil.CountCommitsInRange(options.git_dir,
133*5abab20dSSimon Glass                                                         options.branch)
134*5abab20dSSimon Glass            else:
1352a9e2c6aSSimon Glass                count, msg = gitutil.CountCommitsInBranch(options.git_dir,
136fea5858eSSimon Glass                                                          options.branch)
137cce717a9SSimon Glass            if count is None:
1382a9e2c6aSSimon Glass                sys.exit(col.Color(col.RED, msg))
139*5abab20dSSimon Glass            elif count == 0:
140*5abab20dSSimon Glass                sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
141*5abab20dSSimon Glass                                   options.branch))
1422a9e2c6aSSimon Glass            if msg:
1432a9e2c6aSSimon Glass                print col.Color(col.YELLOW, msg)
144fc3fe1c2SSimon Glass            count += 1   # Build upstream commit also
145fc3fe1c2SSimon Glass
146fc3fe1c2SSimon Glass    if not count:
147fc3fe1c2SSimon Glass        str = ("No commits found to process in branch '%s': "
148fc3fe1c2SSimon Glass               "set branch's upstream or use -c flag" % options.branch)
14931e2141dSMasahiro Yamada        sys.exit(col.Color(col.RED, str))
150fc3fe1c2SSimon Glass
151fc3fe1c2SSimon Glass    # Work out what subset of the boards we are building
152823e60b6SSimon Glass    if not boards:
15373f30b9bSMasahiro Yamada        board_file = os.path.join(options.git, 'boards.cfg')
15473f30b9bSMasahiro Yamada        status = subprocess.call([os.path.join(options.git,
15573f30b9bSMasahiro Yamada                                                'tools/genboardscfg.py')])
15673f30b9bSMasahiro Yamada        if status != 0:
15731e2141dSMasahiro Yamada                sys.exit("Failed to generate boards.cfg")
15873f30b9bSMasahiro Yamada
159fc3fe1c2SSimon Glass        boards = board.Boards()
160fc3fe1c2SSimon Glass        boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
1613cf4ae6fSSimon Glass
1623cf4ae6fSSimon Glass    exclude = []
1633cf4ae6fSSimon Glass    if options.exclude:
1643cf4ae6fSSimon Glass        for arg in options.exclude:
1653cf4ae6fSSimon Glass            exclude += arg.split(',')
1663cf4ae6fSSimon Glass
1673cf4ae6fSSimon Glass    why_selected = boards.SelectBoards(args, exclude)
168fc3fe1c2SSimon Glass    selected = boards.GetSelected()
169fc3fe1c2SSimon Glass    if not len(selected):
17031e2141dSMasahiro Yamada        sys.exit(col.Color(col.RED, 'No matching boards found'))
171fc3fe1c2SSimon Glass
172fc3fe1c2SSimon Glass    # Read the metadata from the commits. First look at the upstream commit,
173fc3fe1c2SSimon Glass    # then the ones in the branch. We would like to do something like
174fc3fe1c2SSimon Glass    # upstream/master~..branch but that isn't possible if upstream/master is
175fc3fe1c2SSimon Glass    # a merge commit (it will list all the commits that form part of the
176fc3fe1c2SSimon Glass    # merge)
177950a2313SSimon Glass    # Conflicting tags are not a problem for buildman, since it does not use
178950a2313SSimon Glass    # them. For example, Series-version is not useful for buildman. On the
179950a2313SSimon Glass    # other hand conflicting tags will cause an error. So allow later tags
180950a2313SSimon Glass    # to overwrite earlier ones by setting allow_overwrite=True
181fea5858eSSimon Glass    if options.branch:
1823b74ba5fSSimon Glass        if count == -1:
183*5abab20dSSimon Glass            if has_range:
184*5abab20dSSimon Glass                range_expr = options.branch
185*5abab20dSSimon Glass            else:
1863b74ba5fSSimon Glass                range_expr = gitutil.GetRangeInBranch(options.git_dir,
1873b74ba5fSSimon Glass                                                      options.branch)
1883b74ba5fSSimon Glass            upstream_commit = gitutil.GetUpstream(options.git_dir,
1893b74ba5fSSimon Glass                                                  options.branch)
190fea5858eSSimon Glass            series = patchstream.GetMetaDataForList(upstream_commit,
191950a2313SSimon Glass                options.git_dir, 1, series=None, allow_overwrite=True)
192fea5858eSSimon Glass
1933b74ba5fSSimon Glass            series = patchstream.GetMetaDataForList(range_expr,
194950a2313SSimon Glass                    options.git_dir, None, series, allow_overwrite=True)
1953b74ba5fSSimon Glass        else:
1963b74ba5fSSimon Glass            # Honour the count
1973b74ba5fSSimon Glass            series = patchstream.GetMetaDataForList(options.branch,
198950a2313SSimon Glass                    options.git_dir, count, series=None, allow_overwrite=True)
199fea5858eSSimon Glass    else:
200fea5858eSSimon Glass        series = None
201e5a0e5d8SSimon Glass        options.verbose = True
20258d818f1SSimon Glass        if not options.summary:
203e5a0e5d8SSimon Glass            options.show_errors = True
204fc3fe1c2SSimon Glass
205fc3fe1c2SSimon Glass    # By default we have one thread per CPU. But if there are not enough jobs
206fc3fe1c2SSimon Glass    # we can have fewer threads and use a high '-j' value for make.
207fc3fe1c2SSimon Glass    if not options.threads:
208fc3fe1c2SSimon Glass        options.threads = min(multiprocessing.cpu_count(), len(selected))
209fc3fe1c2SSimon Glass    if not options.jobs:
210fc3fe1c2SSimon Glass        options.jobs = max(1, (multiprocessing.cpu_count() +
211fc3fe1c2SSimon Glass                len(selected) - 1) / len(selected))
212fc3fe1c2SSimon Glass
213fc3fe1c2SSimon Glass    if not options.step:
214fc3fe1c2SSimon Glass        options.step = len(series.commits) - 1
215fc3fe1c2SSimon Glass
21699796923SMasahiro Yamada    gnu_make = command.Output(os.path.join(options.git,
21799796923SMasahiro Yamada                                           'scripts/show-gnu-make')).rstrip()
21899796923SMasahiro Yamada    if not gnu_make:
21931e2141dSMasahiro Yamada        sys.exit('GNU Make not found')
22099796923SMasahiro Yamada
22105c96b18SSimon Glass    # Create a new builder with the selected options.
22205c96b18SSimon Glass    output_dir = options.output_dir
223fea5858eSSimon Glass    if options.branch:
224f7582ce8SSimon Glass        dirname = options.branch.replace('/', '_')
2255971ab5cSSimon Glass        # As a special case allow the board directory to be placed in the
2265971ab5cSSimon Glass        # output directory itself rather than any subdirectory.
2275971ab5cSSimon Glass        if not options.no_subdirs:
228fea5858eSSimon Glass            output_dir = os.path.join(options.output_dir, dirname)
2290740127fSSimon Glass    if (clean_dir and output_dir != options.output_dir and
2300740127fSSimon Glass            os.path.exists(output_dir)):
231883a321aSSimon Glass        shutil.rmtree(output_dir)
232fc3fe1c2SSimon Glass    builder = Builder(toolchains, output_dir, options.git_dir,
23399796923SMasahiro Yamada            options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
2345971ab5cSSimon Glass            show_unknown=options.show_unknown, step=options.step,
2355971ab5cSSimon Glass            no_subdirs=options.no_subdirs)
236fc3fe1c2SSimon Glass    builder.force_config_on_failure = not options.quick
237d4144e45SSimon Glass    if make_func:
238d4144e45SSimon Glass        builder.do_make = make_func
239fc3fe1c2SSimon Glass
240fc3fe1c2SSimon Glass    # For a dry run, just show our actions as a sanity check
241fc3fe1c2SSimon Glass    if options.dry_run:
242fc3fe1c2SSimon Glass        ShowActions(series, why_selected, selected, builder, options)
243fc3fe1c2SSimon Glass    else:
244fc3fe1c2SSimon Glass        builder.force_build = options.force_build
2454266dc28SSimon Glass        builder.force_build_failures = options.force_build_failures
24697e91526SSimon Glass        builder.force_reconfig = options.force_reconfig
247189a4968SSimon Glass        builder.in_tree = options.in_tree
248fc3fe1c2SSimon Glass
249fc3fe1c2SSimon Glass        # Work out which boards to build
250fc3fe1c2SSimon Glass        board_selected = boards.GetSelectedDict()
251fc3fe1c2SSimon Glass
252fea5858eSSimon Glass        if series:
253fea5858eSSimon Glass            commits = series.commits
254883a321aSSimon Glass            # Number the commits for test purposes
255883a321aSSimon Glass            for commit in range(len(commits)):
256883a321aSSimon Glass                commits[commit].sequence = commit
257fea5858eSSimon Glass        else:
258fea5858eSSimon Glass            commits = None
259fea5858eSSimon Glass
260d4144e45SSimon Glass        Print(GetActionSummary(options.summary, commits, board_selected,
261d4144e45SSimon Glass                                options))
262fc3fe1c2SSimon Glass
2637798e228SSimon Glass        # We can't show function sizes without board details at present
2647798e228SSimon Glass        if options.show_bloat:
2657798e228SSimon Glass            options.show_detail = True
266b2ea7ab2SSimon Glass        builder.SetDisplayOptions(options.show_errors, options.show_sizes,
267ed966657SSimon Glass                                  options.show_detail, options.show_bloat,
268ed966657SSimon Glass                                  options.list_error_boards)
269fc3fe1c2SSimon Glass        if options.summary:
270b2ea7ab2SSimon Glass            builder.ShowSummary(commits, board_selected)
271fc3fe1c2SSimon Glass        else:
2722c3deb97SSimon Glass            fail, warned = builder.BuildBoards(commits, board_selected,
273e5a0e5d8SSimon Glass                                options.keep_outputs, options.verbose)
2742c3deb97SSimon Glass            if fail:
2752c3deb97SSimon Glass                return 128
2762c3deb97SSimon Glass            elif warned:
2772c3deb97SSimon Glass                return 129
2782c3deb97SSimon Glass    return 0
279