xref: /aosp_15_r20/external/toolchain-utils/buildbot_test_toolchains.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li#!/usr/bin/env python3
2*760c253cSXin Li# -*- coding: utf-8 -*-
3*760c253cSXin Li#
4*760c253cSXin Li# Copyright 2016 The ChromiumOS Authors
5*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
6*760c253cSXin Li# found in the LICENSE file.
7*760c253cSXin Li
8*760c253cSXin Li"""Script for running nightly compiler tests on ChromeOS.
9*760c253cSXin Li
10*760c253cSXin LiThis script launches a buildbot to build ChromeOS with the latest compiler on
11*760c253cSXin Lia particular board; then it finds and downloads the trybot image and the
12*760c253cSXin Licorresponding official image, and runs crosperf performance tests comparing
13*760c253cSXin Lithe two.  It then generates a report, emails it to the c-compiler-chrome, as
14*760c253cSXin Liwell as copying the images into the seven-day reports directory.
15*760c253cSXin Li"""
16*760c253cSXin Li
17*760c253cSXin Li# Script to test different toolchains against ChromeOS benchmarks.
18*760c253cSXin Li
19*760c253cSXin Li
20*760c253cSXin Liimport argparse
21*760c253cSXin Liimport datetime
22*760c253cSXin Liimport os
23*760c253cSXin Liimport re
24*760c253cSXin Liimport shutil
25*760c253cSXin Liimport sys
26*760c253cSXin Liimport time
27*760c253cSXin Li
28*760c253cSXin Lifrom cros_utils import buildbot_utils
29*760c253cSXin Lifrom cros_utils import command_executer
30*760c253cSXin Lifrom cros_utils import logger
31*760c253cSXin Li
32*760c253cSXin Li
33*760c253cSXin LiCROSTC_ROOT = "/usr/local/google/crostc"
34*760c253cSXin LiNIGHTLY_TESTS_DIR = os.path.join(CROSTC_ROOT, "nightly-tests")
35*760c253cSXin LiROLE_ACCOUNT = "mobiletc-prebuild"
36*760c253cSXin LiTOOLCHAIN_DIR = os.path.dirname(os.path.realpath(__file__))
37*760c253cSXin LiTMP_TOOLCHAIN_TEST = "/tmp/toolchain-tests"
38*760c253cSXin LiMAIL_PROGRAM = "~/var/bin/mail-detective"
39*760c253cSXin LiPENDING_ARCHIVES_DIR = os.path.join(CROSTC_ROOT, "pending_archives")
40*760c253cSXin LiNIGHTLY_TESTS_RESULTS = os.path.join(CROSTC_ROOT, "nightly_test_reports")
41*760c253cSXin Li
42*760c253cSXin LiIMAGE_DIR = "{board}-{image_type}"
43*760c253cSXin LiIMAGE_VERSION_STR = r"{chrome_version}-{tip}\.{branch}\.{branch_branch}"
44*760c253cSXin LiIMAGE_FS = IMAGE_DIR + "/" + IMAGE_VERSION_STR
45*760c253cSXin LiTRYBOT_IMAGE_FS = IMAGE_FS + "-{build_id}"
46*760c253cSXin LiIMAGE_RE_GROUPS = {
47*760c253cSXin Li    "board": r"(?P<board>\S+)",
48*760c253cSXin Li    "image_type": r"(?P<image_type>\S+)",
49*760c253cSXin Li    "chrome_version": r"(?P<chrome_version>R\d+)",
50*760c253cSXin Li    "tip": r"(?P<tip>\d+)",
51*760c253cSXin Li    "branch": r"(?P<branch>\d+)",
52*760c253cSXin Li    "branch_branch": r"(?P<branch_branch>\d+)",
53*760c253cSXin Li    "build_id": r"(?P<build_id>b\d+)",
54*760c253cSXin Li}
55*760c253cSXin LiTRYBOT_IMAGE_RE = TRYBOT_IMAGE_FS.format(**IMAGE_RE_GROUPS)
56*760c253cSXin Li
57*760c253cSXin LiRECIPE_IMAGE_FS = IMAGE_FS + "-{build_id}-{buildbucket_id}"
58*760c253cSXin LiRECIPE_IMAGE_RE_GROUPS = {
59*760c253cSXin Li    "board": r"(?P<board>\S+)",
60*760c253cSXin Li    "image_type": r"(?P<image_type>\S+)",
61*760c253cSXin Li    "chrome_version": r"(?P<chrome_version>R\d+)",
62*760c253cSXin Li    "tip": r"(?P<tip>\d+)",
63*760c253cSXin Li    "branch": r"(?P<branch>\d+)",
64*760c253cSXin Li    "branch_branch": r"(?P<branch_branch>\d+)",
65*760c253cSXin Li    "build_id": r"(?P<build_id>\d+)",
66*760c253cSXin Li    "buildbucket_id": r"(?P<buildbucket_id>\d+)",
67*760c253cSXin Li}
68*760c253cSXin LiRECIPE_IMAGE_RE = RECIPE_IMAGE_FS.format(**RECIPE_IMAGE_RE_GROUPS)
69*760c253cSXin Li
70*760c253cSXin Li# CL that uses LLVM-Next to build the images (includes chrome).
71*760c253cSXin LiUSE_LLVM_NEXT_PATCH = "513590"
72*760c253cSXin Li
73*760c253cSXin Li
74*760c253cSXin Liclass ToolchainComparator(object):
75*760c253cSXin Li    """Class for doing the nightly tests work."""
76*760c253cSXin Li
77*760c253cSXin Li    def __init__(
78*760c253cSXin Li        self,
79*760c253cSXin Li        board,
80*760c253cSXin Li        remotes,
81*760c253cSXin Li        chromeos_root,
82*760c253cSXin Li        weekday,
83*760c253cSXin Li        patches,
84*760c253cSXin Li        recipe=False,
85*760c253cSXin Li        test=False,
86*760c253cSXin Li        noschedv2=False,
87*760c253cSXin Li        chrome_src="",
88*760c253cSXin Li    ):
89*760c253cSXin Li        self._board = board
90*760c253cSXin Li        self._remotes = remotes
91*760c253cSXin Li        self._chromeos_root = chromeos_root
92*760c253cSXin Li        self._chrome_src = chrome_src
93*760c253cSXin Li        self._base_dir = os.getcwd()
94*760c253cSXin Li        self._ce = command_executer.GetCommandExecuter()
95*760c253cSXin Li        self._l = logger.GetLogger()
96*760c253cSXin Li        self._build = "%s-release-tryjob" % board
97*760c253cSXin Li        self._patches = patches.split(",") if patches else []
98*760c253cSXin Li        self._patches_string = "_".join(str(p) for p in self._patches)
99*760c253cSXin Li        self._recipe = recipe
100*760c253cSXin Li        self._test = test
101*760c253cSXin Li        self._noschedv2 = noschedv2
102*760c253cSXin Li
103*760c253cSXin Li        if not weekday:
104*760c253cSXin Li            self._weekday = time.strftime("%a")
105*760c253cSXin Li        else:
106*760c253cSXin Li            self._weekday = weekday
107*760c253cSXin Li        self._date = datetime.date.today().strftime("%Y/%m/%d")
108*760c253cSXin Li        timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
109*760c253cSXin Li        self._reports_dir = os.path.join(
110*760c253cSXin Li            TMP_TOOLCHAIN_TEST if self._test else NIGHTLY_TESTS_RESULTS,
111*760c253cSXin Li            "%s.%s" % (timestamp, board),
112*760c253cSXin Li        )
113*760c253cSXin Li
114*760c253cSXin Li    def _GetVanillaImageName(self, trybot_image):
115*760c253cSXin Li        """Given a trybot artifact name, get latest vanilla image name.
116*760c253cSXin Li
117*760c253cSXin Li        Args:
118*760c253cSXin Li          trybot_image: artifact name such as
119*760c253cSXin Li            'daisy-release-tryjob/R40-6394.0.0-b1389'
120*760c253cSXin Li            for recipe images, name is in this format:
121*760c253cSXin Li            'lulu-llvm-next-nightly/R84-13037.0.0-31011-8883172717979984032/'
122*760c253cSXin Li
123*760c253cSXin Li        Returns:
124*760c253cSXin Li          Latest official image name, e.g. 'daisy-release/R57-9089.0.0'.
125*760c253cSXin Li        """
126*760c253cSXin Li        # For board names with underscores, we need to fix the trybot image name
127*760c253cSXin Li        # to replace the hyphen (for the recipe builder) with the underscore.
128*760c253cSXin Li        # Currently the only such board we use is 'veyron_tiger'.
129*760c253cSXin Li        if trybot_image.find("veyron-tiger") != -1:
130*760c253cSXin Li            trybot_image = trybot_image.replace("veyron-tiger", "veyron_tiger")
131*760c253cSXin Li        # We need to filter out -tryjob in the trybot_image.
132*760c253cSXin Li        if self._recipe:
133*760c253cSXin Li            trybot = re.sub("-llvm-next-nightly", "-release", trybot_image)
134*760c253cSXin Li            mo = re.search(RECIPE_IMAGE_RE, trybot)
135*760c253cSXin Li        else:
136*760c253cSXin Li            trybot = re.sub("-tryjob", "", trybot_image)
137*760c253cSXin Li            mo = re.search(TRYBOT_IMAGE_RE, trybot)
138*760c253cSXin Li        assert mo
139*760c253cSXin Li        dirname = IMAGE_DIR.replace("\\", "").format(**mo.groupdict())
140*760c253cSXin Li        return buildbot_utils.GetLatestImage(self._chromeos_root, dirname)
141*760c253cSXin Li
142*760c253cSXin Li    def _TestImages(self, trybot_image, vanilla_image):
143*760c253cSXin Li        """Create crosperf experiment file.
144*760c253cSXin Li
145*760c253cSXin Li        Given the names of the trybot, vanilla and non-AFDO images, create the
146*760c253cSXin Li        appropriate crosperf experiment file and launch crosperf on it.
147*760c253cSXin Li        """
148*760c253cSXin Li        if self._test:
149*760c253cSXin Li            experiment_file_dir = TMP_TOOLCHAIN_TEST
150*760c253cSXin Li        else:
151*760c253cSXin Li            experiment_file_dir = os.path.join(NIGHTLY_TESTS_DIR, self._weekday)
152*760c253cSXin Li        experiment_file_name = "%s_toolchain_experiment.txt" % self._board
153*760c253cSXin Li
154*760c253cSXin Li        compiler_string = "llvm"
155*760c253cSXin Li        if USE_LLVM_NEXT_PATCH in self._patches_string:
156*760c253cSXin Li            experiment_file_name = "%s_llvm_next_experiment.txt" % self._board
157*760c253cSXin Li            compiler_string = "llvm_next"
158*760c253cSXin Li
159*760c253cSXin Li        experiment_file = os.path.join(
160*760c253cSXin Li            experiment_file_dir, experiment_file_name
161*760c253cSXin Li        )
162*760c253cSXin Li        experiment_header = """
163*760c253cSXin Li    board: %s
164*760c253cSXin Li    remote: %s
165*760c253cSXin Li    retries: 1
166*760c253cSXin Li    """ % (
167*760c253cSXin Li            self._board,
168*760c253cSXin Li            self._remotes,
169*760c253cSXin Li        )
170*760c253cSXin Li        # TODO(b/244607231): Add graphic benchmarks removed in crrev.com/c/3869851.
171*760c253cSXin Li        experiment_tests = """
172*760c253cSXin Li    benchmark: all_toolchain_perf {
173*760c253cSXin Li      suite: telemetry_Crosperf
174*760c253cSXin Li      iterations: 5
175*760c253cSXin Li      run_local: False
176*760c253cSXin Li    }
177*760c253cSXin Li
178*760c253cSXin Li    benchmark: loading.desktop {
179*760c253cSXin Li      suite: telemetry_Crosperf
180*760c253cSXin Li      test_args: --story-tag-filter=typical
181*760c253cSXin Li      iterations: 3
182*760c253cSXin Li      run_local: False
183*760c253cSXin Li    }
184*760c253cSXin Li
185*760c253cSXin Li    benchmark: platform.ReportDiskUsage {
186*760c253cSXin Li      suite: tast
187*760c253cSXin Li      iterations: 1
188*760c253cSXin Li      run_local: False
189*760c253cSXin Li    }
190*760c253cSXin Li    """
191*760c253cSXin Li
192*760c253cSXin Li        with open(experiment_file, "w", encoding="utf-8") as f:
193*760c253cSXin Li            f.write(experiment_header)
194*760c253cSXin Li            f.write(experiment_tests)
195*760c253cSXin Li
196*760c253cSXin Li            # Now add vanilla to test file.
197*760c253cSXin Li            official_image = """
198*760c253cSXin Li      vanilla_image {
199*760c253cSXin Li        chromeos_root: %s
200*760c253cSXin Li        chrome_src: %s
201*760c253cSXin Li        build: %s
202*760c253cSXin Li        compiler: llvm
203*760c253cSXin Li      }
204*760c253cSXin Li      """ % (
205*760c253cSXin Li                self._chromeos_root,
206*760c253cSXin Li                self._chrome_src,
207*760c253cSXin Li                vanilla_image,
208*760c253cSXin Li            )
209*760c253cSXin Li            f.write(official_image)
210*760c253cSXin Li
211*760c253cSXin Li            label_string = "%s_trybot_image" % compiler_string
212*760c253cSXin Li
213*760c253cSXin Li            # Reuse autotest files from vanilla image for trybot images
214*760c253cSXin Li            autotest_files = os.path.join(
215*760c253cSXin Li                "/tmp", vanilla_image, "autotest_files"
216*760c253cSXin Li            )
217*760c253cSXin Li            experiment_image = """
218*760c253cSXin Li      %s {
219*760c253cSXin Li        chromeos_root: %s
220*760c253cSXin Li        chrome_src: %s
221*760c253cSXin Li        build: %s
222*760c253cSXin Li        autotest_path: %s
223*760c253cSXin Li        compiler: %s
224*760c253cSXin Li      }
225*760c253cSXin Li      """ % (
226*760c253cSXin Li                label_string,
227*760c253cSXin Li                self._chromeos_root,
228*760c253cSXin Li                self._chrome_src,
229*760c253cSXin Li                trybot_image,
230*760c253cSXin Li                autotest_files,
231*760c253cSXin Li                compiler_string,
232*760c253cSXin Li            )
233*760c253cSXin Li            f.write(experiment_image)
234*760c253cSXin Li
235*760c253cSXin Li        crosperf = os.path.join(TOOLCHAIN_DIR, "crosperf", "crosperf")
236*760c253cSXin Li        noschedv2_opts = "--noschedv2" if self._noschedv2 else ""
237*760c253cSXin Li        no_email = not self._test
238*760c253cSXin Li        command = (
239*760c253cSXin Li            f"{crosperf} --no_email={no_email} "
240*760c253cSXin Li            f"--results_dir={self._reports_dir} --logging_level=verbose "
241*760c253cSXin Li            f"--json_report=True {noschedv2_opts} {experiment_file}"
242*760c253cSXin Li        )
243*760c253cSXin Li
244*760c253cSXin Li        return self._ce.RunCommand(command)
245*760c253cSXin Li
246*760c253cSXin Li    def _SendEmail(self):
247*760c253cSXin Li        """Find email message generated by crosperf and send it."""
248*760c253cSXin Li        filename = os.path.join(self._reports_dir, "msg_body.html")
249*760c253cSXin Li        if os.path.exists(filename) and os.path.exists(
250*760c253cSXin Li            os.path.expanduser(MAIL_PROGRAM)
251*760c253cSXin Li        ):
252*760c253cSXin Li            email_title = "buildbot llvm test results"
253*760c253cSXin Li            if USE_LLVM_NEXT_PATCH in self._patches_string:
254*760c253cSXin Li                email_title = "buildbot llvm_next test results"
255*760c253cSXin Li            command = 'cat %s | %s -s "%s, %s %s" -team -html' % (
256*760c253cSXin Li                filename,
257*760c253cSXin Li                MAIL_PROGRAM,
258*760c253cSXin Li                email_title,
259*760c253cSXin Li                self._board,
260*760c253cSXin Li                self._date,
261*760c253cSXin Li            )
262*760c253cSXin Li            self._ce.RunCommand(command)
263*760c253cSXin Li
264*760c253cSXin Li    def _CopyJson(self):
265*760c253cSXin Li        # Make sure a destination directory exists.
266*760c253cSXin Li        os.makedirs(PENDING_ARCHIVES_DIR, exist_ok=True)
267*760c253cSXin Li        # Copy json report to pending archives directory.
268*760c253cSXin Li        command = "cp %s/*.json %s/." % (
269*760c253cSXin Li            self._reports_dir,
270*760c253cSXin Li            PENDING_ARCHIVES_DIR,
271*760c253cSXin Li        )
272*760c253cSXin Li        ret = self._ce.RunCommand(command)
273*760c253cSXin Li        # Failing to access json report means that crosperf terminated or all tests
274*760c253cSXin Li        # failed, raise an error.
275*760c253cSXin Li        if ret != 0:
276*760c253cSXin Li            raise RuntimeError(
277*760c253cSXin Li                "Crosperf failed to run tests, cannot copy json report!"
278*760c253cSXin Li            )
279*760c253cSXin Li
280*760c253cSXin Li    def DoAll(self):
281*760c253cSXin Li        """Main function inside ToolchainComparator class.
282*760c253cSXin Li
283*760c253cSXin Li        Launch trybot, get image names, create crosperf experiment file, run
284*760c253cSXin Li        crosperf, and copy images into seven-day report directories.
285*760c253cSXin Li        """
286*760c253cSXin Li        if self._recipe:
287*760c253cSXin Li            print("Using recipe buckets to get latest image.")
288*760c253cSXin Li            # crbug.com/1077313: Some boards are not consistently
289*760c253cSXin Li            # spelled, having underscores in some places and dashes in others.
290*760c253cSXin Li            # The image directories consistenly use dashes, so convert underscores
291*760c253cSXin Li            # to dashes to work around this.
292*760c253cSXin Li            trybot_image = buildbot_utils.GetLatestRecipeImage(
293*760c253cSXin Li                self._chromeos_root,
294*760c253cSXin Li                "%s-llvm-next-nightly" % self._board.replace("_", "-"),
295*760c253cSXin Li            )
296*760c253cSXin Li        else:
297*760c253cSXin Li            # Launch tryjob and wait to get image location.
298*760c253cSXin Li            buildbucket_id, trybot_image = buildbot_utils.GetTrybotImage(
299*760c253cSXin Li                self._chromeos_root,
300*760c253cSXin Li                self._build,
301*760c253cSXin Li                self._patches,
302*760c253cSXin Li                tryjob_flags=["--notests"],
303*760c253cSXin Li                build_toolchain=True,
304*760c253cSXin Li            )
305*760c253cSXin Li            print(
306*760c253cSXin Li                "trybot_url: \
307*760c253cSXin Li            http://cros-goldeneye/chromeos/healthmonitoring/buildDetails?buildbucketId=%s"
308*760c253cSXin Li                % buildbucket_id
309*760c253cSXin Li            )
310*760c253cSXin Li
311*760c253cSXin Li        if not trybot_image:
312*760c253cSXin Li            self._l.LogError("Unable to find trybot_image!")
313*760c253cSXin Li            return 2
314*760c253cSXin Li
315*760c253cSXin Li        vanilla_image = self._GetVanillaImageName(trybot_image)
316*760c253cSXin Li
317*760c253cSXin Li        print("trybot_image: %s" % trybot_image)
318*760c253cSXin Li        print("vanilla_image: %s" % vanilla_image)
319*760c253cSXin Li
320*760c253cSXin Li        ret = self._TestImages(trybot_image, vanilla_image)
321*760c253cSXin Li        # Always try to send report email as crosperf will generate report when
322*760c253cSXin Li        # tests partially succeeded.
323*760c253cSXin Li        if not self._test:
324*760c253cSXin Li            self._SendEmail()
325*760c253cSXin Li            self._CopyJson()
326*760c253cSXin Li        # Non-zero ret here means crosperf tests partially failed, raise error here
327*760c253cSXin Li        # so that toolchain summary report can catch it.
328*760c253cSXin Li        if ret != 0:
329*760c253cSXin Li            raise RuntimeError("Crosperf tests partially failed!")
330*760c253cSXin Li
331*760c253cSXin Li        return 0
332*760c253cSXin Li
333*760c253cSXin Li
334*760c253cSXin Lidef Main(argv):
335*760c253cSXin Li    """The main function."""
336*760c253cSXin Li
337*760c253cSXin Li    # Common initializations
338*760c253cSXin Li    command_executer.InitCommandExecuter()
339*760c253cSXin Li    parser = argparse.ArgumentParser()
340*760c253cSXin Li    parser.add_argument(
341*760c253cSXin Li        "--remote", dest="remote", help="Remote machines to run tests on."
342*760c253cSXin Li    )
343*760c253cSXin Li    parser.add_argument(
344*760c253cSXin Li        "--board", dest="board", default="x86-zgb", help="The target board."
345*760c253cSXin Li    )
346*760c253cSXin Li    parser.add_argument(
347*760c253cSXin Li        "--chromeos_root",
348*760c253cSXin Li        dest="chromeos_root",
349*760c253cSXin Li        help="The chromeos root from which to run tests.",
350*760c253cSXin Li    )
351*760c253cSXin Li    parser.add_argument(
352*760c253cSXin Li        "--chrome_src",
353*760c253cSXin Li        dest="chrome_src",
354*760c253cSXin Li        default="",
355*760c253cSXin Li        help="The path to the source of chrome. "
356*760c253cSXin Li        "This is used to run telemetry benchmarks. "
357*760c253cSXin Li        "The default one is the src inside chroot.",
358*760c253cSXin Li    )
359*760c253cSXin Li    parser.add_argument(
360*760c253cSXin Li        "--weekday",
361*760c253cSXin Li        default="",
362*760c253cSXin Li        dest="weekday",
363*760c253cSXin Li        help="The day of the week for which to run tests.",
364*760c253cSXin Li    )
365*760c253cSXin Li    parser.add_argument(
366*760c253cSXin Li        "--patch",
367*760c253cSXin Li        dest="patches",
368*760c253cSXin Li        help="The patches to use for the testing, "
369*760c253cSXin Li        "seprate the patch numbers with ',' "
370*760c253cSXin Li        "for more than one patches.",
371*760c253cSXin Li    )
372*760c253cSXin Li    parser.add_argument(
373*760c253cSXin Li        "--noschedv2",
374*760c253cSXin Li        dest="noschedv2",
375*760c253cSXin Li        action="store_true",
376*760c253cSXin Li        default=False,
377*760c253cSXin Li        help="Pass --noschedv2 to crosperf.",
378*760c253cSXin Li    )
379*760c253cSXin Li    parser.add_argument(
380*760c253cSXin Li        "--recipe",
381*760c253cSXin Li        dest="recipe",
382*760c253cSXin Li        default=True,
383*760c253cSXin Li        help="Use images generated from recipe rather than"
384*760c253cSXin Li        "launching tryjob to get images.",
385*760c253cSXin Li    )
386*760c253cSXin Li    parser.add_argument(
387*760c253cSXin Li        "--test",
388*760c253cSXin Li        dest="test",
389*760c253cSXin Li        default=False,
390*760c253cSXin Li        help="Test this script on local desktop, "
391*760c253cSXin Li        "disabling mobiletc checking and email sending."
392*760c253cSXin Li        "Artifacts stored in /tmp/toolchain-tests",
393*760c253cSXin Li    )
394*760c253cSXin Li
395*760c253cSXin Li    options = parser.parse_args(argv[1:])
396*760c253cSXin Li    if not options.board:
397*760c253cSXin Li        print("Please give a board.")
398*760c253cSXin Li        return 1
399*760c253cSXin Li    if not options.remote:
400*760c253cSXin Li        print("Please give at least one remote machine.")
401*760c253cSXin Li        return 1
402*760c253cSXin Li    if not options.chromeos_root:
403*760c253cSXin Li        print("Please specify the ChromeOS root directory.")
404*760c253cSXin Li        return 1
405*760c253cSXin Li    if options.test:
406*760c253cSXin Li        print("Cleaning local test directory for this script.")
407*760c253cSXin Li        if os.path.exists(TMP_TOOLCHAIN_TEST):
408*760c253cSXin Li            shutil.rmtree(TMP_TOOLCHAIN_TEST)
409*760c253cSXin Li        os.mkdir(TMP_TOOLCHAIN_TEST)
410*760c253cSXin Li
411*760c253cSXin Li    fc = ToolchainComparator(
412*760c253cSXin Li        options.board,
413*760c253cSXin Li        options.remote,
414*760c253cSXin Li        options.chromeos_root,
415*760c253cSXin Li        options.weekday,
416*760c253cSXin Li        options.patches,
417*760c253cSXin Li        options.recipe,
418*760c253cSXin Li        options.test,
419*760c253cSXin Li        options.noschedv2,
420*760c253cSXin Li        chrome_src=options.chrome_src,
421*760c253cSXin Li    )
422*760c253cSXin Li    return fc.DoAll()
423*760c253cSXin Li
424*760c253cSXin Li
425*760c253cSXin Liif __name__ == "__main__":
426*760c253cSXin Li    retval = Main(sys.argv)
427*760c253cSXin Li    sys.exit(retval)
428