def _foo_impl(ctx): out = ctx.actions.declare_file(ctx.label.name) ctx.actions.write( output = out, content = "Hello!\n", ) return [DefaultInfo(files = depset([out]))] foo_binary = rule( implementation = _foo_impl, ) def _bundle_impl(ctx): """ bundle binary target into a folder which can later be used to make a post to github easily. """ binary_target = ctx.attr.binary binary = binary_target[DefaultInfo].files.to_list()[0] # First files are binary runfiles_files = binary_target[DefaultInfo].default_runfiles.files.to_list() # Name as output directory out_dir = ctx.actions.declare_directory(ctx.label.name) copy_cmd = [] copy_cmd.append("mkdir -p {}".format(out_dir.path)) for f in runfiles_files: if f.path == binary.path: continue start = 0 for directory in f.path.split("/"): if directory == binary.short_path.split("/")[0]: break start += 1 # Remove the first folder (output) and last file (actaul files that needed to be copied) paths = "/".join(f.path.split("/")[start:-1]) full_path = "{}/{}".format(out_dir.path, paths) copy_cmd.append("mkdir -p {}".format(full_path)) copy_cmd.append("cp {} {}".format(f.path, full_path)) copy_cmd.append("cp {} {}".format(binary.path, out_dir.path)) ctx.actions.run_shell( inputs = runfiles_files, outputs = [out_dir], command = " && ".join(copy_cmd), progress_message = "Bundling {}".format(ctx.label.name), ) return [DefaultInfo(files = depset([out_dir]))] bundle = rule( implementation = _bundle_impl, attrs = { "binary": attr.label( doc = "The cc_binary target to bundle", providers = [DefaultInfo], ), }, ) def _bun_binary_impl(ctx): out = ctx.actions.declare_file("bun") ctx.actions.run_shell( inputs = ctx.files.srcs, outputs = [out], command = """ mkdir -p {outdir} unzip -j {src} {inner} -d {outdir} chmod +x {outdir}/bun """.format( outdir = out.dirname, src = ctx.files.srcs[0].path, inner = ctx.attr.src_folder, out = out.path, ), ) return DefaultInfo( files = depset([out]), executable = out, ) bun_binary = rule( implementation = _bun_binary_impl, attrs = { "srcs": attr.label_list(allow_files=True), "src_folder": attr.string(), }, executable = True, ) def _bun_build_impl(ctx): """ Run bun build on the folder This sucks because you need to either copy node module into the root folder where main.ts file exists or copy everything outwards. I chose to do it in this way. TODO: If possible, maybe create a node_module inside of the main target path and create a symlink TODO: Add a specific path for node_modules """ out = ctx.actions.declare_file(ctx.label.name + ".js") inputs = [ctx.file.src] + ctx.files.data ctx.actions.run_shell( inputs = inputs, outputs = [out], tools = [ctx.executable._bun] + inputs, command = """ cp -r third_party/bun/** . \ && cp -r {src_folder}/** . \ && cp -r ./bazel-out/k8-fastbuild/bin/hg-web/src/** src \ && ls src \ && export NODE_PATH=./node_modules && {bun_path} build {input_path} --outfile {output_path} --target browser """.format( bun_path = ctx.executable._bun.path, src_folder = ctx.attr.src_folder, # Fix this lol input_path = "/".join(ctx.file.src.path.split("/")[-2:]), output_path = out.path, ), progress_message = "Bundling {} with Bun!\n\n".format(ctx.file.src.path), ) return [DefaultInfo(files=depset([out]))] bun_build = rule( implementation = _bun_build_impl, attrs = { "src": attr.label(allow_single_file = [".ts", ".tsx", ".js", ".jsx"]), "_bun": attr.label( default = Label("//third_party/bun:bun"), executable = True, cfg = "exec", ), "data": attr.label_list(allow_files=True), "src_folder": attr.string(), }, ) def _bun_run_impl(ctx): actual_exe = ctx.actions.declare_file(ctx.label.name) # 1. Get the workspace name (crucial for runfiles paths) workspace_name = ctx.workspace_name # 2. Define the paths relative to the runfiles root bun_path = ctx.executable._bun.short_path src_path = ctx.file.src.short_path # 3. Create the launcher script # We use a template to handle the environment and pathing script_content = """#!/bin/bash # Find the runfiles directory if [[ -z "$RUNFILES_DIR" ]]; then if [[ -d "$0.runfiles" ]]; then RUNFILES_DIR="$0.runfiles" fi fi # Navigate to the workspace root inside runfiles cd "$RUNFILES_DIR/{workspace}" pwd # Execute bun with the src file and any extra arguments exec "./{bun_bin}" run "./{src_file}" "$@" """.format( workspace = workspace_name, bun_bin = bun_path, src_file = src_path ) ctx.actions.write( output = actual_exe, content = script_content, is_executable = True, ) return [ DefaultInfo( executable = actual_exe, runfiles = ctx.runfiles( files = [ctx.file.src] + ctx.files.data + ctx.files._bun_files ).merge(ctx.attr._bun[DefaultInfo].default_runfiles), ), ] # TODO: Fix the rules so that it can do relative import. bun_run = rule( implementation = _bun_run_impl, executable = True, attrs = { "src": attr.label(allow_single_file = True, mandatory = True), "data": attr.label_list(allow_files = True), "_bun": attr.label( default = Label("//third_party/bun:bun"), executable = True, cfg = "exec" ), "_bun_files": attr.label(default = Label("//third_party/bun:bun_files")), }, ) def _move_files_into_dir_impl(ctx): srcs = ctx.files.srcs outs = [] for src in srcs: out = ctx.actions.declare_file(ctx.attr.dest + "/" + src.basename) ctx.actions.run_shell( inputs = [src], outputs = [out], command = "cp -r \"$1\" \"$2\"", arguments = [src.path, out.path], ) outs.append(out) return [DefaultInfo(files = depset(outs))] move_files_into_dir = rule( implementation = _move_files_into_dir_impl, attrs = { "srcs": attr.label_list(allow_files=True), "dest": attr.string(), }, ) def _move_to_directory_impl(ctx): srcs = ctx.files.data res = [] for src in srcs: true = "/".join(src.path.split("/")[2:]) path = ctx.attr.dest + "/" + true if ctx.attr.dest != "" else true out = ctx.actions.declare_file( path ); ctx.actions.symlink( output = out, target_file = src, ) res.append(out) return [DefaultInfo(files = depset(res))] move_to_directory = rule( implementation = _move_to_directory_impl, attrs = { "data": attr.label_list(allow_files=True), "dest": attr.string(), }, ) def _macos_app_impl(ctx): """Implementation for creating a macOS .app bundle from a binary.""" binary = ctx.file.binary app_name = ctx.attr.app_name bundle_id = ctx.attr.bundle_id # Declare the .app directory as output app_dir = ctx.actions.declare_directory(app_name + ".app") ctx.actions.run_shell( inputs = [binary], outputs = [app_dir], command = """ set -e APPDIR="{app_dir}" rm -rf "$APPDIR" mkdir -p "$APPDIR/Contents/MacOS" mkdir -p "$APPDIR/Contents/Resources" cp {binary} "$APPDIR/Contents/MacOS/{app_name}" chmod +x "$APPDIR/Contents/MacOS/{app_name}" cat > "$APPDIR/Contents/Info.plist" < CFBundleExecutable {app_name} CFBundleIdentifier {bundle_id} CFBundleName {app_name} CFBundleVersion 1.0 EOF """.format( app_dir = app_dir.path, binary = binary.path, app_name = app_name, bundle_id = bundle_id, ), progress_message = "Creating macOS app bundle for {}".format(app_name), ) return [DefaultInfo(files = depset([app_dir]))] _macos_app = rule( implementation = _macos_app_impl, attrs = { "binary": attr.label( allow_single_file = True, mandatory = True, doc = "The binary to bundle into a .app", ), "app_name": attr.string( mandatory = True, doc = "Name of the application", ), "bundle_id": attr.string( mandatory = True, doc = "Bundle identifier (e.g., com.example.app)", ), }, ) def _macos_signed_app_impl(ctx): """Implementation for signing a macOS .app bundle.""" app_dir = ctx.file.app app_name = ctx.attr.app_name # Declare the signed .app directory as output signed_app_dir = ctx.actions.declare_directory(app_name + "_signed.app") ctx.actions.run_shell( inputs = [app_dir], outputs = [signed_app_dir], command = """ set -e APPDIR="{app_dir}" SIGNEDDIR="{signed_dir}" rm -rf "$SIGNEDDIR" # Use -L to dereference symlinks, as codesign doesn't work with symlinks cp -RL "$APPDIR" "$SIGNEDDIR" codesign --deep --force --sign - "$SIGNEDDIR" """.format( app_dir = app_dir.path, signed_dir = signed_app_dir.path, ), progress_message = "Signing macOS app bundle {}".format(app_name), ) return [DefaultInfo(files = depset([signed_app_dir]))] _macos_signed_app = rule( implementation = _macos_signed_app_impl, attrs = { "app": attr.label( allow_single_file = True, mandatory = True, doc = "The .app directory to sign", ), "app_name": attr.string( mandatory = True, doc = "Name of the application", ), }, ) def macos_app_and_dmg(name, binary, bundle_id = "com.example.app"): """ Creates a macOS .app bundle, signs it, and packages it into a DMG. Args: name: Base name for the generated targets binary: Label of the binary target to bundle bundle_id: Bundle identifier for the app (default: com.example.app) Generates: {name}_app: The .app bundle {name}_signed_app: The signed .app bundle {name}_dmg: The final DMG file """ macos_constraint = ["@platforms//os:macos"] # 1. Build the .app bundle _macos_app( name = name + "_app", binary = binary, app_name = name, bundle_id = bundle_id, target_compatible_with = macos_constraint, ) # 2. Sign the .app _macos_signed_app( name = name + "_signed_app", app = ":" + name + "_app", app_name = name, target_compatible_with = macos_constraint, ) # 3. Create the DMG native.genrule( name = name + "_dmg", srcs = [":" + name + "_signed_app"], outs = [name + ".dmg"], cmd = """ set -e SIGNEDDIR="$(location :{name}_signed_app)" hdiutil create \ -volname {name} \ -srcfolder "$$SIGNEDDIR" \ -ov -format UDZO "$@" """.format(name = name), local = 1, # Disable sandboxing for hdiutil tags = ["no-sandbox"], target_compatible_with = macos_constraint, )