github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/gclient.bzl (about)

     1  def _gclient_repository_rule(ctx):
     2      ctx.report_progress("Configuring Gclient...")
     3      _config(ctx)
     4      ctx.report_progress("Cloning {}...".format(ctx.attr.remote))
     5      _clone(ctx)
     6      ctx.report_progress("Applying required patches: {}...".format(ctx.attr.patches))
     7      _patch(ctx)
     8      ctx.report_progress("Syncing to {}...".format(ctx.attr.revision))
     9      _sync(ctx)
    10      ctx.report_progress("Saving version...")
    11      _version(ctx)
    12      ctx.report_progress("Optionally stripping...")
    13      _strip(ctx)
    14      ctx.report_progress("Running gn gen...")
    15      _gn(ctx)
    16  
    17      ctx.report_progress("Trimming build.ninja file from regeneration...")
    18      _trim_build(ctx)
    19      ctx.report_progress("Trimming extra files...")
    20      _clean_extra_files(ctx)
    21      ctx.report_progress("Adding BUILD file...")
    22      _add_build_file(ctx)
    23  
    24  def _config(ctx):
    25      if __is_windows(ctx) and len(ctx.attr.gclient_vars_windows) > 0:
    26          __execute(ctx, __prefix(ctx, "gclient") + ["config", ctx.attr.remote, "--custom-var=" + ctx.attr.gclient_vars_windows])
    27      else:
    28          __execute(ctx, __prefix(ctx, "gclient") + ["config", ctx.attr.remote])
    29  
    30  def _clone(ctx):
    31      __execute(ctx, __prefix(ctx, "git") + ["clone", ctx.attr.remote])
    32  
    33  def _sync(ctx):
    34      __execute(ctx, __prefix(ctx, "gclient") + ["sync", "-r", ctx.attr.revision, "--shallow"])
    35  
    36  def _patch(ctx):
    37      for arg in ctx.attr.patch_args:
    38          if not arg.startswith("-p"):
    39              return False
    40  
    41      for patchfile in ctx.attr.patches:
    42          ctx.patch(patchfile, int(ctx.attr.patch_args[-1][2:]))
    43  
    44      # gclient sync will complain otherwise
    45      # this method misses added files, which must be added with `git add .`, but we
    46      # can add them when we need them.
    47      if ctx.attr.patches:
    48          __execute(ctx, __prefix(ctx, "git") + ["config", "user.email", "foundry-x@google.com"], wd = ctx.attr.base_dir)
    49          __execute(ctx, __prefix(ctx, "git") + ["config", "user.name", "Foundry X CI"], wd = ctx.attr.base_dir)
    50          __execute(ctx, __prefix(ctx, "git") + ["commit", "-am."], wd = ctx.attr.base_dir)
    51  
    52  def _version(ctx):
    53      command = __prefix(ctx, "git") + ["log", "-1", "--pretty=format:%H@%ct"]
    54      st = ctx.execute(command, environment = __env(ctx), working_directory = ctx.attr.base_dir)
    55      if st.return_code != 0:
    56          __error(ctx.name, command, st.stdout, st.stderr)
    57      ctx.file("version", st.stdout)
    58  
    59  def _add_build_file(ctx):
    60      if ctx.attr.build_file:
    61          ctx.file("BUILD.bazel", ctx.read(ctx.attr.build_file))
    62      elif ctx.attr.build_file_content:
    63          ctx.file("BUILD.bazel", ctx.attr.build_file_content)
    64  
    65  def _strip(ctx):
    66      root = ctx.path(".")
    67      if ctx.attr.strip_prefix:
    68          target_dir = "{}/{}".format(root, ctx.attr.strip_prefix)
    69          if not ctx.path(target_dir).exists:
    70              fail("strip_prefix at {} does not exist in repo".format(ctx.attr.strip_prefix))
    71          for item in ctx.path(target_dir).readdir():
    72              ctx.symlink(item, root.get_child(item.basename))
    73  
    74  def _gn(ctx):
    75      if __is_macos(ctx):
    76          # We cannot condition this repository rule on whether we want to build for an Intel target
    77          # or an Arm target. Thus we will generate the ninja files for both Intel and Arm in
    78          # different output directories, and conditionally run ninja in one of the output
    79          # directories depending on configuration.
    80          gn_args = ctx.attr.gn_args_macos_x86
    81          __execute(ctx, __prefix(ctx, "gn") + ["gen", "out", "--args={}".format(gn_args)], wd = ctx.attr.base_dir)
    82          gn_args = ctx.attr.gn_args_macos_arm64
    83          __execute(ctx, __prefix(ctx, "gn") + ["gen", "out_arm64", "--args={}".format(gn_args)], wd = ctx.attr.base_dir)
    84          return
    85      if __is_windows(ctx):
    86          # We cannot condition this repository rule on whether we want to build debug or not.
    87          # Thus we will generate the ninja files for both release and debug in
    88          # different output directories, and conditionally run ninja in one of the output
    89          # directories depending on configuration.
    90          __execute(ctx, __prefix(ctx, "gn") + ["gen", "out", "--args={}".format(ctx.attr.gn_args_windows)], wd = ctx.attr.base_dir)
    91          __execute(ctx, __prefix(ctx, "gn") + ["gen", "out_dbg", "--args={}".format(ctx.attr.gn_args_windows_dbg)], wd = ctx.attr.base_dir)
    92          return
    93      __execute(ctx, __prefix(ctx, "gn") + ["gen", "out", "--args={}".format(ctx.attr.gn_args_linux)], wd = ctx.attr.base_dir)
    94  
    95  # When using sandbox, ninja will think it has to rebuild "build.ninja"
    96  # using gn, but we don't want to build steps to rely on gn at all.
    97  def _trim_build(ctx):
    98      # https://stackoverflow.com/questions/5694228/sed-in-place-flag-that-works-both-on-mac-bsd-and-linux
    99      __execute(ctx, __prefix(ctx, "sed") + ["-i.bak", "-e", "s/^build build\\.ninja.*$//g", "out/build.ninja"], wd = ctx.attr.base_dir)
   100      __execute(ctx, __prefix(ctx, "sed") + ["-i.bak", "-e", "s/^  generator.*$//g", "out/build.ninja"], wd = ctx.attr.base_dir)
   101      __execute(ctx, __prefix(ctx, "sed") + ["-i.bak", "-e", "s/^  depfile.*$//g", "out/build.ninja"], wd = ctx.attr.base_dir)
   102      __execute(ctx, __prefix(ctx, "rm") + ["-f", "out/build.ninja.bak"], wd = ctx.attr.base_dir)
   103  
   104  def _clean_extra_files(ctx):
   105      __execute(ctx, __prefix(ctx, "find") + [".", "-name", "BUILD", "-delete"])
   106      __execute(ctx, __prefix(ctx, "find") + [".", "-name", "BUILD.bazel", "-delete"])
   107      __execute(ctx, __prefix(ctx, "find") + [".", "-name", "WORKSPACE", "-delete"])
   108      __execute(ctx, __prefix(ctx, "find") + [".", "-name", "WORKSPACE.bazel", "-delete"])
   109  
   110      # Defining all the actual required files on //external/BUILD.goma is hard,
   111      # so instead we trim anything that may affect our action cacheability.
   112      delPaths = ["*/.git*", "*/.cipd*", "*/depot_tools*", "*/testdata*", "*/test/data*"]
   113  
   114      if __is_windows(ctx):
   115          for i, path in enumerate(delPaths):
   116              delPaths[i] = "'" + path + "'"
   117  
   118      if __is_windows(ctx) or __is_macos(ctx):
   119          __execute(ctx, __prefix(ctx, "find") + [".", "-path", "'*/buildtools/third_party/libc++*'", "-delete"])
   120          __execute(ctx, __prefix(ctx, "find") + [".", "-path", "'*/__pycache__*'", "-delete"])
   121  
   122      for path in delPaths:
   123          __execute(ctx, __prefix(ctx, "find") + [".", "-path", path, "-delete"])
   124  
   125      # symlink loop? /private/var/tmp/_bazel_ukai/8e91f546e4f29bd6c4479ddfa3f9b1f4/external/goma/client/build/mac_files/xcode_binaries/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.3/Headers/ruby/ruby
   126      __execute(ctx, __prefix(ctx, "find") + [".", "-type", "l", "-name", "ruby", "-delete"])
   127  
   128      # ERROR: /private/var/tmp/_bazel_ukai/8e91f546e4f29bd6c4479ddfa3f9b1f4/external/goma/BUILD.bazel:1:10: @goma//:srcs: invalid label 'client/build/mac_files/xcode_binaries/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man/man3/APR::Base64.3pm' in element 27843 of attribute 'srcs' in 'filegroup' rule: invalid target name 'client/build/mac_files/xcode_binaries/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man/man3/APR::Base64.3pm': target names may not contain ':'
   129      __execute(ctx, __prefix(ctx, "find") + [".", "-path", "*/share/man/*", "-delete"])
   130  
   131      __execute(ctx, __prefix(ctx, "find") + [".", "-name", "WORKSPACE.bazel", "-delete"])
   132  
   133  def __execute(ctx, command, wd = ""):
   134      st = ctx.execute(command, environment = __env(ctx), working_directory = wd)
   135      if st.return_code != 0:
   136          __error(ctx.name, command, st.stdout, st.stderr, __env(ctx))
   137  
   138  def __is_windows(ctx):
   139      return ctx.os.name.lower().find("windows") != -1
   140  
   141  def __is_macos(ctx):
   142      return ctx.os.name == "mac os x"
   143  
   144  def __env(ctx):
   145      if __is_windows(ctx):
   146          osenv = ctx.os.environ
   147          osenv["PATH"] = "C:\\src\\depot_tools;" + osenv["PATH"]
   148          osenv["DEPOT_TOOLS_WIN_TOOLCHAIN"] = "0"
   149          return osenv
   150      return ctx.os.environ
   151  
   152  def __prefix(ctx, cmd):
   153      if __is_windows(ctx):
   154          return ["cmd", "/c", cmd]
   155      return [cmd]
   156  
   157  def __error(name, command, stdout, stderr, environ):
   158      command_text = " ".join([str(item).strip() for item in command])
   159      fail("error running '%s' while working with @%s:\nstdout:%s\nstderr:%s\nenviron:%s" % (command_text, name, stdout, stderr, environ))
   160  
   161  gclient_repository = repository_rule(
   162      implementation = _gclient_repository_rule,
   163      attrs = {
   164          "remote": attr.string(
   165              mandatory = True,
   166              doc = "The URI of the remote repository.",
   167          ),
   168          "revision": attr.string(
   169              mandatory = True,
   170              doc = "Specific revision to be checked out.",
   171          ),
   172          "strip_prefix": attr.string(
   173              doc = "Whether to strip a prefix folder.",
   174          ),
   175          "base_dir": attr.string(
   176              doc = "Base dir to run gn from.",
   177          ),
   178          "gclient_vars_windows": attr.string(
   179              doc = "Gclient custom vars on windows.",
   180          ),
   181          "gn_args_linux": attr.string(
   182              doc = "Args to pass to gn on linux.",
   183          ),
   184          "gn_args_macos_arm64": attr.string(
   185              doc = "Args to pass to gn on macos for Arm target.",
   186          ),
   187          "gn_args_macos_x86": attr.string(
   188              doc = "Args to pass to gn on macos for x86_64 target.",
   189          ),
   190          "gn_args_windows": attr.string(
   191              doc = "Args to pass to gn on windows.",
   192          ),
   193          "gn_args_windows_dbg": attr.string(
   194              doc = "Args to pass to gn on windows.",
   195          ),
   196          "build_file": attr.label(
   197              allow_single_file = True,
   198              doc = "The file to be used as the BUILD file for this repo. Takes" +
   199                    "precedence over build_file_content.",
   200          ),
   201          "build_file_content": attr.string(
   202              doc = "The content for the BUILD file for this repository. " +
   203                    "Either build_file or build_file_content must be specified.",
   204          ),
   205          "patches": attr.label_list(
   206              default = [],
   207              doc =
   208                  "A list of files that are to be applied as patches after" +
   209                  "extracting the archive. **ONLY** supports Bazel-native patch" +
   210                  "implementation.",
   211          ),
   212          "patch_args": attr.string_list(
   213              default = ["-p0"],
   214              doc =
   215                  "The arguments given to the patch tool. Defaults to -p0." +
   216                  "**ONLY** supports -p#.",
   217          ),
   218      },
   219  )