view gui_ze/gui_ze.bzl @ 207:58d9b64d8dca

Updated deployment script to include sqlite3
author MrJuneJune <me@mrjunejune.com>
date Sun, 15 Feb 2026 12:25:50 -0800
parents 9f4429c49733
children
line wrap: on
line source

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_bundle_impl(ctx):
  """
  Bundle TypeScript/JavaScript with Bun, resolving imports from bazel root.

  Copies all dependencies (dereferencing symlinks) to create a flat structure
  where imports can resolve correctly relative to the bazel workspace root.
  """
  out = ctx.actions.declare_file(ctx.label.name + ".js")

  inputs = depset(
    direct = [ctx.file.src],
    transitive = [dep[DefaultInfo].files for dep in ctx.attr.deps]
  )

  # Get the source file's package directory (e.g., "hg-web" from "hg-web/src/main.tsx")
  src_package = ctx.file.src.path.split("/")[0]

  # Collect unique root directories to copy (deduped), excluding src_package (handled separately)
  dirs_to_copy = {}
  for f in ctx.files.deps:
    # Find the root directory path by locating where short_path starts in full path
    short_path_suffix = "/".join(f.short_path.split("/")[1:])
    pos = f.path.find(short_path_suffix)
    if pos > 0:
      root_dir = f.path[:pos].rstrip("/")
      # Skip src_package - it's a symlink that needs special handling
      if not root_dir.endswith(src_package):
        dirs_to_copy[root_dir] = True

  # Build copy commands for each unique directory
  copy_commands = ["cp -rL {dir} .".format(dir = d) for d in dirs_to_copy.keys()]

  ctx.actions.run_shell(
    inputs = inputs,
    outputs = [out],
    tools = [ctx.executable._bun] + [ctx.file.src] + ctx.files.deps + ctx.files.node_modules,
    command = """
cp -rL {src_package} {src_package}_tmp && rm -rf {src_package} && mv {src_package}_tmp {src_package}
{copy_commands}
export NODE_PATH=./third_party/bun/node_modules
cp ./third_party/bun/tsconfig.json .
{bun} build {entry} --outfile {output} --target browser 
""".format(
      copy_commands = "\n".join(copy_commands),
      src_package = src_package,
      bun = ctx.executable._bun.path,
      entry = ctx.file.src.path,
      output = out.path,
    ),
    progress_message = "Bundling %s with Bun" % ctx.file.src.short_path,
  )

  return [DefaultInfo(files = depset([out]))]

bun_bundle = rule(
  implementation = _bun_bundle_impl,
  attrs = {
    "src": attr.label(
      allow_single_file = [".ts", ".tsx", ".js", ".jsx"],
      doc = "Entry point file to bundle",
    ),
    "deps": attr.label_list(
      allow_files = True,
      doc = "Source files and other dependencies to include",
    ),
    "node_modules": attr.label_list(
      allow_files = True,
      default = [Label("//third_party/bun:bun_files")],
      doc = "Node modules for bundling (defaults to //third_party/bun:bun_files)",
    ),
    "_bun": attr.label(
      default = Label("//third_party/bun:bun"),
      executable = True,
      cfg = "exec",
    ),
  },
  doc = "Bundle TypeScript/JavaScript using Bun with bazel root imports",
)



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 \
      && pwd \
      && 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), runfiles = ctx.runfiles(files = 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" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleExecutable</key>
  <string>{app_name}</string>
  <key>CFBundleIdentifier</key>
  <string>{bundle_id}</string>
  <key>CFBundleName</key>
  <string>{app_name}</string>
  <key>CFBundleVersion</key>
  <string>1.0</string>
</dict>
</plist>
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,
    )