view gui_ze/gui_ze.bzl @ 179:8d17f6e6e290

[ThirdParty] Added emsdk bazel rules that can be supported by bazel 9.0.0
author MrJuneJune <me@mrjunejune.com>
date Thu, 22 Jan 2026 21:23:17 -0800
parents f3084bca7317
children 71ad34a8bc9a 8c74204fd362
line wrap: on
line source

def _wasm_binary_impl(ctx):
    """
    Compile C source to WASM using clang --target=wasm32.
    No libc - suitable for standalone WASM modules.

    Requires LLVM with wasm32 support (e.g., Homebrew LLVM on macOS).
    """
    src = ctx.file.src
    out = ctx.actions.declare_file(ctx.label.name + ".wasm")

    # Shell script to find clang with wasm support and set PATH for wasm-ld
    setup_cmd = """
    export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
    if [ -x /opt/homebrew/opt/llvm/bin/clang ]; then
      CLANG=/opt/homebrew/opt/llvm/bin/clang
    elif [ -x /usr/local/opt/llvm/bin/clang ]; then
      CLANG=/usr/local/opt/llvm/bin/clang
    else
      CLANG=clang
    fi
    """

    # Build clang command
    cmd_parts = [
        setup_cmd,
        "$CLANG",
        "--target=wasm32-unknown-unknown",
        "-O2",
        "-Wl,--no-entry",
        "-Wl,--export-dynamic",
    ]

    # Add exported functions
    for fn in ctx.attr.exports:
        cmd_parts.append("-Wl,--export=" + fn)

    cmd_parts.append("-o")
    cmd_parts.append(out.path)
    cmd_parts.append(src.path)

    ctx.actions.run_shell(
        inputs = [src],
        outputs = [out],
        command = " ".join(cmd_parts),
        progress_message = "Compiling {} to WASM".format(src.basename),
        execution_requirements = {"no-sandbox": "1"},
    )

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

wasm_binary = rule(
    implementation = _wasm_binary_impl,
    attrs = {
        "src": attr.label(
            allow_single_file = [".c"],
            mandatory = True,
            doc = "The C source file to compile",
        ),
        "exports": attr.string_list(
            default = [],
            doc = "List of function names to export (without leading underscore)",
        ),
    },
)

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}/** .  \
      && export NODE_PATH=./node_modules && {bun_path} build {input_path} --outfile {output_path}
      """.format(
      bun_path = ctx.executable._bun.path,
      src_folder = ctx.attr.src_folder,
      input_path = ctx.file.src.path.split("/")[-1],
      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 \"$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" <<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,
    )