1*d4726bddSHONG Yifan# Copyright 2020 The Bazel Authors. All rights reserved. 2*d4726bddSHONG Yifan# 3*d4726bddSHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License"); 4*d4726bddSHONG Yifan# you may not use this file except in compliance with the License. 5*d4726bddSHONG Yifan# You may obtain a copy of the License at 6*d4726bddSHONG Yifan# 7*d4726bddSHONG Yifan# http://www.apache.org/licenses/LICENSE-2.0 8*d4726bddSHONG Yifan# 9*d4726bddSHONG Yifan# Unless required by applicable law or agreed to in writing, software 10*d4726bddSHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS, 11*d4726bddSHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*d4726bddSHONG Yifan# See the License for the specific language governing permissions and 13*d4726bddSHONG Yifan# limitations under the License. 14*d4726bddSHONG Yifan 15*d4726bddSHONG Yifan"""A module defining clippy rules""" 16*d4726bddSHONG Yifan 17*d4726bddSHONG Yifanload("//rust/private:common.bzl", "rust_common") 18*d4726bddSHONG Yifanload("//rust/private:providers.bzl", "CaptureClippyOutputInfo", "ClippyInfo") 19*d4726bddSHONG Yifanload( 20*d4726bddSHONG Yifan "//rust/private:rustc.bzl", 21*d4726bddSHONG Yifan "collect_deps", 22*d4726bddSHONG Yifan "collect_inputs", 23*d4726bddSHONG Yifan "construct_arguments", 24*d4726bddSHONG Yifan) 25*d4726bddSHONG Yifanload( 26*d4726bddSHONG Yifan "//rust/private:utils.bzl", 27*d4726bddSHONG Yifan "determine_output_hash", 28*d4726bddSHONG Yifan "find_cc_toolchain", 29*d4726bddSHONG Yifan "find_toolchain", 30*d4726bddSHONG Yifan) 31*d4726bddSHONG Yifan 32*d4726bddSHONG YifanClippyFlagsInfo = provider( 33*d4726bddSHONG Yifan doc = "Pass each value as an additional flag to clippy invocations", 34*d4726bddSHONG Yifan fields = {"clippy_flags": "List[string] Flags to pass to clippy"}, 35*d4726bddSHONG Yifan) 36*d4726bddSHONG Yifan 37*d4726bddSHONG Yifandef _clippy_flag_impl(ctx): 38*d4726bddSHONG Yifan return ClippyFlagsInfo(clippy_flags = [f for f in ctx.build_setting_value if f != ""]) 39*d4726bddSHONG Yifan 40*d4726bddSHONG Yifanclippy_flag = rule( 41*d4726bddSHONG Yifan doc = ( 42*d4726bddSHONG Yifan "Add a custom clippy flag from the command line with `--@rules_rust//:clippy_flag`." + 43*d4726bddSHONG Yifan "Multiple uses are accumulated and appended after the extra_rustc_flags." 44*d4726bddSHONG Yifan ), 45*d4726bddSHONG Yifan implementation = _clippy_flag_impl, 46*d4726bddSHONG Yifan build_setting = config.string(flag = True, allow_multiple = True), 47*d4726bddSHONG Yifan) 48*d4726bddSHONG Yifan 49*d4726bddSHONG Yifandef _clippy_flags_impl(ctx): 50*d4726bddSHONG Yifan return ClippyFlagsInfo(clippy_flags = ctx.build_setting_value) 51*d4726bddSHONG Yifan 52*d4726bddSHONG Yifanclippy_flags = rule( 53*d4726bddSHONG Yifan doc = ( 54*d4726bddSHONG Yifan "Add custom clippy flags from the command line with `--@rules_rust//:clippy_flags`." 55*d4726bddSHONG Yifan ), 56*d4726bddSHONG Yifan implementation = _clippy_flags_impl, 57*d4726bddSHONG Yifan build_setting = config.string_list(flag = True), 58*d4726bddSHONG Yifan) 59*d4726bddSHONG Yifan 60*d4726bddSHONG Yifandef _get_clippy_ready_crate_info(target, aspect_ctx = None): 61*d4726bddSHONG Yifan """Check that a target is suitable for clippy and extract the `CrateInfo` provider from it. 62*d4726bddSHONG Yifan 63*d4726bddSHONG Yifan Args: 64*d4726bddSHONG Yifan target (Target): The target the aspect is running on. 65*d4726bddSHONG Yifan aspect_ctx (ctx, optional): The aspect's context object. 66*d4726bddSHONG Yifan 67*d4726bddSHONG Yifan Returns: 68*d4726bddSHONG Yifan CrateInfo, optional: A `CrateInfo` provider if clippy should be run or `None`. 69*d4726bddSHONG Yifan """ 70*d4726bddSHONG Yifan 71*d4726bddSHONG Yifan # Ignore external targets 72*d4726bddSHONG Yifan if target.label.workspace_root.startswith("external"): 73*d4726bddSHONG Yifan return None 74*d4726bddSHONG Yifan 75*d4726bddSHONG Yifan # Targets with specific tags will not be formatted 76*d4726bddSHONG Yifan if aspect_ctx: 77*d4726bddSHONG Yifan ignore_tags = [ 78*d4726bddSHONG Yifan "noclippy", 79*d4726bddSHONG Yifan "no-clippy", 80*d4726bddSHONG Yifan ] 81*d4726bddSHONG Yifan 82*d4726bddSHONG Yifan for tag in ignore_tags: 83*d4726bddSHONG Yifan if tag in aspect_ctx.rule.attr.tags: 84*d4726bddSHONG Yifan return None 85*d4726bddSHONG Yifan 86*d4726bddSHONG Yifan # Obviously ignore any targets that don't contain `CrateInfo` 87*d4726bddSHONG Yifan if rust_common.crate_info in target: 88*d4726bddSHONG Yifan return target[rust_common.crate_info] 89*d4726bddSHONG Yifan elif rust_common.test_crate_info in target: 90*d4726bddSHONG Yifan return target[rust_common.test_crate_info].crate 91*d4726bddSHONG Yifan else: 92*d4726bddSHONG Yifan return None 93*d4726bddSHONG Yifan 94*d4726bddSHONG Yifandef _clippy_aspect_impl(target, ctx): 95*d4726bddSHONG Yifan crate_info = _get_clippy_ready_crate_info(target, ctx) 96*d4726bddSHONG Yifan if not crate_info: 97*d4726bddSHONG Yifan return [ClippyInfo(output = depset([]))] 98*d4726bddSHONG Yifan 99*d4726bddSHONG Yifan toolchain = find_toolchain(ctx) 100*d4726bddSHONG Yifan cc_toolchain, feature_configuration = find_cc_toolchain(ctx) 101*d4726bddSHONG Yifan 102*d4726bddSHONG Yifan dep_info, build_info, _ = collect_deps( 103*d4726bddSHONG Yifan deps = crate_info.deps, 104*d4726bddSHONG Yifan proc_macro_deps = crate_info.proc_macro_deps, 105*d4726bddSHONG Yifan aliases = crate_info.aliases, 106*d4726bddSHONG Yifan ) 107*d4726bddSHONG Yifan 108*d4726bddSHONG Yifan compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs( 109*d4726bddSHONG Yifan ctx, 110*d4726bddSHONG Yifan ctx.rule.file, 111*d4726bddSHONG Yifan ctx.rule.files, 112*d4726bddSHONG Yifan # Clippy doesn't need to invoke transitive linking, therefore doesn't need linkstamps. 113*d4726bddSHONG Yifan depset([]), 114*d4726bddSHONG Yifan toolchain, 115*d4726bddSHONG Yifan cc_toolchain, 116*d4726bddSHONG Yifan feature_configuration, 117*d4726bddSHONG Yifan crate_info, 118*d4726bddSHONG Yifan dep_info, 119*d4726bddSHONG Yifan build_info, 120*d4726bddSHONG Yifan ) 121*d4726bddSHONG Yifan 122*d4726bddSHONG Yifan args, env = construct_arguments( 123*d4726bddSHONG Yifan ctx = ctx, 124*d4726bddSHONG Yifan attr = ctx.rule.attr, 125*d4726bddSHONG Yifan file = ctx.file, 126*d4726bddSHONG Yifan toolchain = toolchain, 127*d4726bddSHONG Yifan tool_path = toolchain.clippy_driver.path, 128*d4726bddSHONG Yifan cc_toolchain = cc_toolchain, 129*d4726bddSHONG Yifan feature_configuration = feature_configuration, 130*d4726bddSHONG Yifan crate_info = crate_info, 131*d4726bddSHONG Yifan dep_info = dep_info, 132*d4726bddSHONG Yifan linkstamp_outs = linkstamp_outs, 133*d4726bddSHONG Yifan ambiguous_libs = ambiguous_libs, 134*d4726bddSHONG Yifan output_hash = determine_output_hash(crate_info.root, ctx.label), 135*d4726bddSHONG Yifan rust_flags = [], 136*d4726bddSHONG Yifan out_dir = out_dir, 137*d4726bddSHONG Yifan build_env_files = build_env_files, 138*d4726bddSHONG Yifan build_flags_files = build_flags_files, 139*d4726bddSHONG Yifan emit = ["dep-info", "metadata"], 140*d4726bddSHONG Yifan skip_expanding_rustc_env = True, 141*d4726bddSHONG Yifan ) 142*d4726bddSHONG Yifan 143*d4726bddSHONG Yifan if crate_info.is_test: 144*d4726bddSHONG Yifan args.rustc_flags.add("--test") 145*d4726bddSHONG Yifan 146*d4726bddSHONG Yifan clippy_flags = ctx.attr._clippy_flags[ClippyFlagsInfo].clippy_flags 147*d4726bddSHONG Yifan 148*d4726bddSHONG Yifan if hasattr(ctx.attr, "_clippy_flag"): 149*d4726bddSHONG Yifan clippy_flags = clippy_flags + ctx.attr._clippy_flag[ClippyFlagsInfo].clippy_flags 150*d4726bddSHONG Yifan 151*d4726bddSHONG Yifan # For remote execution purposes, the clippy_out file must be a sibling of crate_info.output 152*d4726bddSHONG Yifan # or rustc may fail to create intermediate output files because the directory does not exist. 153*d4726bddSHONG Yifan if ctx.attr._capture_output[CaptureClippyOutputInfo].capture_output: 154*d4726bddSHONG Yifan clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.out", sibling = crate_info.output) 155*d4726bddSHONG Yifan args.process_wrapper_flags.add("--stderr-file", clippy_out) 156*d4726bddSHONG Yifan 157*d4726bddSHONG Yifan if clippy_flags: 158*d4726bddSHONG Yifan args.rustc_flags.add_all(clippy_flags) 159*d4726bddSHONG Yifan 160*d4726bddSHONG Yifan # If we are capturing the output, we want the build system to be able to keep going 161*d4726bddSHONG Yifan # and consume the output. Some clippy lints are denials, so we cap everything at warn. 162*d4726bddSHONG Yifan args.rustc_flags.add("--cap-lints=warn") 163*d4726bddSHONG Yifan else: 164*d4726bddSHONG Yifan # A marker file indicating clippy has executed successfully. 165*d4726bddSHONG Yifan # This file is necessary because "ctx.actions.run" mandates an output. 166*d4726bddSHONG Yifan clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.ok", sibling = crate_info.output) 167*d4726bddSHONG Yifan args.process_wrapper_flags.add("--touch-file", clippy_out) 168*d4726bddSHONG Yifan 169*d4726bddSHONG Yifan if clippy_flags: 170*d4726bddSHONG Yifan args.rustc_flags.add_all(clippy_flags) 171*d4726bddSHONG Yifan else: 172*d4726bddSHONG Yifan # The user didn't provide any clippy flags explicitly so we apply conservative defaults. 173*d4726bddSHONG Yifan 174*d4726bddSHONG Yifan # Turn any warnings from clippy or rustc into an error, as otherwise 175*d4726bddSHONG Yifan # Bazel will consider the execution result of the aspect to be "success", 176*d4726bddSHONG Yifan # and Clippy won't be re-triggered unless the source file is modified. 177*d4726bddSHONG Yifan args.rustc_flags.add("-Dwarnings") 178*d4726bddSHONG Yifan 179*d4726bddSHONG Yifan # Upstream clippy requires one of these two filenames or it silently uses 180*d4726bddSHONG Yifan # the default config. Enforce the naming so users are not confused. 181*d4726bddSHONG Yifan valid_config_file_names = [".clippy.toml", "clippy.toml"] 182*d4726bddSHONG Yifan if ctx.file._config.basename not in valid_config_file_names: 183*d4726bddSHONG Yifan fail("The clippy config file must be named one of: {}".format(valid_config_file_names)) 184*d4726bddSHONG Yifan env["CLIPPY_CONF_DIR"] = "${{pwd}}/{}".format(ctx.file._config.dirname) 185*d4726bddSHONG Yifan compile_inputs = depset([ctx.file._config], transitive = [compile_inputs]) 186*d4726bddSHONG Yifan 187*d4726bddSHONG Yifan ctx.actions.run( 188*d4726bddSHONG Yifan executable = ctx.executable._process_wrapper, 189*d4726bddSHONG Yifan inputs = compile_inputs, 190*d4726bddSHONG Yifan outputs = [clippy_out], 191*d4726bddSHONG Yifan env = env, 192*d4726bddSHONG Yifan tools = [toolchain.clippy_driver], 193*d4726bddSHONG Yifan arguments = args.all, 194*d4726bddSHONG Yifan mnemonic = "Clippy", 195*d4726bddSHONG Yifan toolchain = "@rules_rust//rust:toolchain_type", 196*d4726bddSHONG Yifan ) 197*d4726bddSHONG Yifan 198*d4726bddSHONG Yifan return [ 199*d4726bddSHONG Yifan OutputGroupInfo(clippy_checks = depset([clippy_out])), 200*d4726bddSHONG Yifan ClippyInfo(output = depset([clippy_out])), 201*d4726bddSHONG Yifan ] 202*d4726bddSHONG Yifan 203*d4726bddSHONG Yifan# Example: Run the clippy checker on all targets in the codebase. 204*d4726bddSHONG Yifan# bazel build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect \ 205*d4726bddSHONG Yifan# --output_groups=clippy_checks \ 206*d4726bddSHONG Yifan# //... 207*d4726bddSHONG Yifanrust_clippy_aspect = aspect( 208*d4726bddSHONG Yifan fragments = ["cpp"], 209*d4726bddSHONG Yifan attrs = { 210*d4726bddSHONG Yifan "_capture_output": attr.label( 211*d4726bddSHONG Yifan doc = "Value of the `capture_clippy_output` build setting", 212*d4726bddSHONG Yifan default = Label("//:capture_clippy_output"), 213*d4726bddSHONG Yifan ), 214*d4726bddSHONG Yifan "_cc_toolchain": attr.label( 215*d4726bddSHONG Yifan doc = ( 216*d4726bddSHONG Yifan "Required attribute to access the cc_toolchain. See [Accessing the C++ toolchain]" + 217*d4726bddSHONG Yifan "(https://docs.bazel.build/versions/master/integrating-with-rules-cc.html#accessing-the-c-toolchain)" 218*d4726bddSHONG Yifan ), 219*d4726bddSHONG Yifan default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), 220*d4726bddSHONG Yifan ), 221*d4726bddSHONG Yifan "_clippy_flag": attr.label( 222*d4726bddSHONG Yifan doc = "Arguments to pass to clippy." + 223*d4726bddSHONG Yifan "Multiple uses are accumulated and appended after the extra_rustc_flags.", 224*d4726bddSHONG Yifan default = Label("//:clippy_flag"), 225*d4726bddSHONG Yifan ), 226*d4726bddSHONG Yifan "_clippy_flags": attr.label( 227*d4726bddSHONG Yifan doc = "Arguments to pass to clippy", 228*d4726bddSHONG Yifan default = Label("//:clippy_flags"), 229*d4726bddSHONG Yifan ), 230*d4726bddSHONG Yifan "_config": attr.label( 231*d4726bddSHONG Yifan doc = "The `clippy.toml` file used for configuration", 232*d4726bddSHONG Yifan allow_single_file = True, 233*d4726bddSHONG Yifan default = Label("//:clippy.toml"), 234*d4726bddSHONG Yifan ), 235*d4726bddSHONG Yifan "_error_format": attr.label( 236*d4726bddSHONG Yifan doc = "The desired `--error-format` flags for clippy", 237*d4726bddSHONG Yifan default = "//:error_format", 238*d4726bddSHONG Yifan ), 239*d4726bddSHONG Yifan "_extra_rustc_flag": attr.label( 240*d4726bddSHONG Yifan default = Label("//:extra_rustc_flag"), 241*d4726bddSHONG Yifan ), 242*d4726bddSHONG Yifan "_per_crate_rustc_flag": attr.label( 243*d4726bddSHONG Yifan default = Label("//:experimental_per_crate_rustc_flag"), 244*d4726bddSHONG Yifan ), 245*d4726bddSHONG Yifan "_process_wrapper": attr.label( 246*d4726bddSHONG Yifan doc = "A process wrapper for running clippy on all platforms", 247*d4726bddSHONG Yifan default = Label("//util/process_wrapper"), 248*d4726bddSHONG Yifan executable = True, 249*d4726bddSHONG Yifan cfg = "exec", 250*d4726bddSHONG Yifan ), 251*d4726bddSHONG Yifan }, 252*d4726bddSHONG Yifan provides = [ClippyInfo], 253*d4726bddSHONG Yifan required_providers = [ 254*d4726bddSHONG Yifan [rust_common.crate_info], 255*d4726bddSHONG Yifan [rust_common.test_crate_info], 256*d4726bddSHONG Yifan ], 257*d4726bddSHONG Yifan toolchains = [ 258*d4726bddSHONG Yifan str(Label("//rust:toolchain_type")), 259*d4726bddSHONG Yifan "@bazel_tools//tools/cpp:toolchain_type", 260*d4726bddSHONG Yifan ], 261*d4726bddSHONG Yifan implementation = _clippy_aspect_impl, 262*d4726bddSHONG Yifan doc = """\ 263*d4726bddSHONG YifanExecutes the clippy checker on specified targets. 264*d4726bddSHONG Yifan 265*d4726bddSHONG YifanThis aspect applies to existing rust_library, rust_test, and rust_binary rules. 266*d4726bddSHONG Yifan 267*d4726bddSHONG YifanAs an example, if the following is defined in `examples/hello_lib/BUILD.bazel`: 268*d4726bddSHONG Yifan 269*d4726bddSHONG Yifan```python 270*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") 271*d4726bddSHONG Yifan 272*d4726bddSHONG Yifanrust_library( 273*d4726bddSHONG Yifan name = "hello_lib", 274*d4726bddSHONG Yifan srcs = ["src/lib.rs"], 275*d4726bddSHONG Yifan) 276*d4726bddSHONG Yifan 277*d4726bddSHONG Yifanrust_test( 278*d4726bddSHONG Yifan name = "greeting_test", 279*d4726bddSHONG Yifan srcs = ["tests/greeting.rs"], 280*d4726bddSHONG Yifan deps = [":hello_lib"], 281*d4726bddSHONG Yifan) 282*d4726bddSHONG Yifan``` 283*d4726bddSHONG Yifan 284*d4726bddSHONG YifanThen the targets can be analyzed with clippy using the following command: 285*d4726bddSHONG Yifan 286*d4726bddSHONG Yifan```output 287*d4726bddSHONG Yifan$ bazel build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect \ 288*d4726bddSHONG Yifan --output_groups=clippy_checks //hello_lib:all 289*d4726bddSHONG Yifan``` 290*d4726bddSHONG Yifan""", 291*d4726bddSHONG Yifan) 292*d4726bddSHONG Yifan 293*d4726bddSHONG Yifandef _rust_clippy_rule_impl(ctx): 294*d4726bddSHONG Yifan clippy_ready_targets = [dep for dep in ctx.attr.deps if "clippy_checks" in dir(dep[OutputGroupInfo])] 295*d4726bddSHONG Yifan files = depset([], transitive = [dep[OutputGroupInfo].clippy_checks for dep in clippy_ready_targets]) 296*d4726bddSHONG Yifan return [DefaultInfo(files = files)] 297*d4726bddSHONG Yifan 298*d4726bddSHONG Yifanrust_clippy = rule( 299*d4726bddSHONG Yifan implementation = _rust_clippy_rule_impl, 300*d4726bddSHONG Yifan attrs = { 301*d4726bddSHONG Yifan "deps": attr.label_list( 302*d4726bddSHONG Yifan doc = "Rust targets to run clippy on.", 303*d4726bddSHONG Yifan providers = [ 304*d4726bddSHONG Yifan [rust_common.crate_info], 305*d4726bddSHONG Yifan [rust_common.test_crate_info], 306*d4726bddSHONG Yifan ], 307*d4726bddSHONG Yifan aspects = [rust_clippy_aspect], 308*d4726bddSHONG Yifan ), 309*d4726bddSHONG Yifan }, 310*d4726bddSHONG Yifan doc = """\ 311*d4726bddSHONG YifanExecutes the clippy checker on a specific target. 312*d4726bddSHONG Yifan 313*d4726bddSHONG YifanSimilar to `rust_clippy_aspect`, but allows specifying a list of dependencies \ 314*d4726bddSHONG Yifanwithin the build system. 315*d4726bddSHONG Yifan 316*d4726bddSHONG YifanFor example, given the following example targets: 317*d4726bddSHONG Yifan 318*d4726bddSHONG Yifan```python 319*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") 320*d4726bddSHONG Yifan 321*d4726bddSHONG Yifanrust_library( 322*d4726bddSHONG Yifan name = "hello_lib", 323*d4726bddSHONG Yifan srcs = ["src/lib.rs"], 324*d4726bddSHONG Yifan) 325*d4726bddSHONG Yifan 326*d4726bddSHONG Yifanrust_test( 327*d4726bddSHONG Yifan name = "greeting_test", 328*d4726bddSHONG Yifan srcs = ["tests/greeting.rs"], 329*d4726bddSHONG Yifan deps = [":hello_lib"], 330*d4726bddSHONG Yifan) 331*d4726bddSHONG Yifan``` 332*d4726bddSHONG Yifan 333*d4726bddSHONG YifanRust clippy can be set as a build target with the following: 334*d4726bddSHONG Yifan 335*d4726bddSHONG Yifan```python 336*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_clippy") 337*d4726bddSHONG Yifan 338*d4726bddSHONG Yifanrust_clippy( 339*d4726bddSHONG Yifan name = "hello_library_clippy", 340*d4726bddSHONG Yifan testonly = True, 341*d4726bddSHONG Yifan deps = [ 342*d4726bddSHONG Yifan ":hello_lib", 343*d4726bddSHONG Yifan ":greeting_test", 344*d4726bddSHONG Yifan ], 345*d4726bddSHONG Yifan) 346*d4726bddSHONG Yifan``` 347*d4726bddSHONG Yifan""", 348*d4726bddSHONG Yifan) 349*d4726bddSHONG Yifan 350*d4726bddSHONG Yifandef _capture_clippy_output_impl(ctx): 351*d4726bddSHONG Yifan """Implementation of the `capture_clippy_output` rule 352*d4726bddSHONG Yifan 353*d4726bddSHONG Yifan Args: 354*d4726bddSHONG Yifan ctx (ctx): The rule's context object 355*d4726bddSHONG Yifan 356*d4726bddSHONG Yifan Returns: 357*d4726bddSHONG Yifan list: A list containing the CaptureClippyOutputInfo provider 358*d4726bddSHONG Yifan """ 359*d4726bddSHONG Yifan return [CaptureClippyOutputInfo(capture_output = ctx.build_setting_value)] 360*d4726bddSHONG Yifan 361*d4726bddSHONG Yifancapture_clippy_output = rule( 362*d4726bddSHONG Yifan doc = "Control whether to print clippy output or store it to a file, using the configured error_format.", 363*d4726bddSHONG Yifan implementation = _capture_clippy_output_impl, 364*d4726bddSHONG Yifan build_setting = config.bool(flag = True), 365*d4726bddSHONG Yifan) 366