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 )