GithubHelp home page GithubHelp logo

lalten / rules_appimage Goto Github PK

View Code? Open in Web Editor NEW
10.0 1.0 2.0 87 KB

Bazel rules for creating AppImage packages

License: MIT License

Starlark 47.50% Python 44.56% Shell 5.44% C++ 2.50%
appimage bazel

rules_appimage's Introduction

rules_appimage

CI status MIT License Awesome

rules_appimage provides a Bazel rule for packaging existing binary targets into AppImage packages.

AppImages are a great match for Bazel because the runfiles structure and launcher stub (where applicable) can be packaged into an AppRun structure relatively easily. No application source modifications are required. No existing Bazel targets have to be modified.

The appimage rule has been used successfully with py_binary, ruby_binary, sh_binary, and cc_binary. In fact, any lang_binary should be compatible.

Getting Started

Installation

See the latest release notes for a snippet to add to your WORKSPACE, or use the following:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

RULES_APPIMAGE_VER = "1.1.0"

http_archive(
    name = "rules_appimage",
    # sha256 = "",  # FIXME: Bazel will print the proper value to add here during the first build.
    strip_prefix = "rules_appimage-{}".format(RULES_APPIMAGE_VER),
    url = "https://github.com/lalten/rules_appimage/archive/refs/tags/v{}.tar.gz".format(RULES_APPIMAGE_VER),
)

load("@rules_appimage//:deps.bzl", "rules_appimage_deps")

rules_appimage_deps()

Usage

To package a binary target into an AppImage, you add a appimage rule and point it at the target. So in your BUILD files you do:

load("@rules_appimage//appimage:appimage.bzl", "appimage")

appimage(
    name = "program.appimage",
    binary = ":program",
)

There is also a appimage_test rule that takes the same arguments but runs the appimage as a Bazel test target.

How to use the appimage

Via Bazel

You can bazel run AppImages directly:

❯ bazel run -- //tests:appimage_py
(...)
Hello, world!
❯ bazel run -- //tests:appimage_py --name TheAssassin
(...)
Hello, TheAssassin!

Directly

The resulting AppImage file is a portable standalone executable (which is kind of the point of the whole thing!)

❯ file bazel-bin/tests/appimage_py
bazel-bin/tests/appimage_py: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

❯ bazel-bin/tests/appimage_py --name GitHub
Hello, GitHub!
❯ rsync bazel-bin/tests/appimage_py my-server:. && ssh my-server ./appimage_py
Hello, world!

AppImage CLI args

There are certain AppImage CLI args. They'll still work. Try --appimage-help.

❯ bazel run -- //tests:appimage_py --appimage-version
(...)
AppImage runtime version: https://github.com/lalten/type2-runtime/releases/tag/build-2022-10-03-c5c7b07

Troubleshooting

$PWD is a Read-only file system

When the AppImage is run, it will mount contained the SquashFS image via FUSE as read-only file system.

If this causes problems, you can:

  • Write to $BUILD_WORKING_DIRECTORY instead, which is set by Bazel when running bazel run, and set by rules_appimage when running as pure AppImage.
  • Set the APPIMAGE_EXTRACT_AND_RUN env var or pass the --appimage-extract-and-run CLI arg to extract the AppImage into a temporary directory and run it from there.

Missing fusermount

rules_appimage builds type-2 AppImages using a statically-linked appimage runtime. The only runtime dependency is either fusermount (from fuse2) or fusermount3 (from fuse3). If neither is not available, you'll get an error like this:

fuse: failed to exec fusermount3: No such file or directory

Cannot mount AppImage, please check your FUSE setup.
You might still be able to extract the contents of this AppImage
if you run it with the --appimage-extract option.
See https://github.com/AppImage/AppImageKit/wiki/FUSE
for more information
open dir error: No such file or directory

In this case, you can:

  • Install libfuse3: sudo apt install libfuse3
  • Run the application with --appimage-extract-and-run as the first command-line argument.
  • Set the APPIMAGE_EXTRACT_AND_RUN environment variable.

The latter two options will cause the appimage to extract the files instead of mounting them directly. This may take slightly longer and consume more disk space.

Missing runtime deps

The AppImage will only be as portable/hermetic/reproducible as the rest of your Bazel build is.

Example: Without a hermetic Python toolchain your target will use the system's Python interpreter. If your program needs Python >=3.8 but you run the appimage on a host that uses Python 3.6 by default, you might get an error like this:

  File "/tmp/appimage_extracted_544993ad2a5919e445b618f1fe009e53/test_py.runfiles/rules_appimage/tests/test.py", line 10
    assert (s := DATA_DEP.stat().st_size) == 591, f"{DATA_DEP} has wrong size {s}"
              ^
SyntaxError: invalid syntax

Check https://thundergolfer.com/bazel/python/2021/06/25/a-basic-python-bazel-toolchain/ if you would like to know more.

Something isn't right about my appimage, how can I debug?

An easy way to understand what is happening inside the appimage is to run the application with the --appimage-extract cli arg. This will extract the bundled squashfs blob into a squashfs-root dir in the current working directory.

You can look at those files and see exactly what's going on. In fact, you can even run squashfs-root/AppRun and it will run exactly the same as with the packaged appimage. This can be very handy when rebuilding the Bazel target is not the best option but you need to modify a file inside.

Contributing

Issue reports and pull requests are welcome.

Please test your changes:

bazel test //...

And run the linters:

buildifier -lint=fix -warnings all -r .
markdownlint .
yamllint --strict .
pylint .
pycodestyle .
flake8 .
black .
mypy --install-types .
isort .
vulture .
pydocstyle .

rules_appimage's People

Contributors

lalten avatar renovate[bot] avatar woutervdstoel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

rules_appimage's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

bazel
WORKSPACE
  • rules_python 0.24.0
deps.bzl
  • bazel_skylib 1.4.2
bazelisk
.bazelversion
  • bazel 6.3.1
github-actions
.github/workflows/ci.yaml
  • actions/checkout v3
  • actions/cache v3
  • actions/checkout v3
  • actions/cache v3
  • actions/setup-node v3
  • xt0rted/markdownlint-problem-matcher v2
pip-compile
requirements.in
  • pytest ==7.3.2
pip_requirements
requirements.txt
  • iniconfig ==2.0.0
  • packaging ==23.0
  • pluggy ==1.0.0
  • pytest ==7.3.2

  • Check this box to trigger a request for Renovate to run again on this repository

`$0` different between AppImage env and `bazel run`

Hi, it seems like the entry point for an AppImage is the symlink to the binary located within the runfiles dir, and not the binary itself located alongside the runfiles dir. This breaks (at the very least) the runfiles mecanism for bash provided by @bazel_tools: https://github.com/bazelbuild/bazel/blob/master/tools/bash/runfiles/runfiles.bash

I have a minimally reproducing example:

BUILD:

sh_binary(
  name = "foo",
  srcs = ["foo.sh"],
)

appimage(
  name = "foo.appimage",
  binary = ":foo",
)

foo.sh:

#!/bin/bash

echo $0
ls $0.runfiles

We can then do:

$ bazel run :foo
INFO: Analyzed target //:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:foo up-to-date:
  bazel-bin/foo
INFO: Elapsed time: 0.246s, Critical Path: 0.01s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/foo
/home/kbalke/.cache/bazel/_bazel_kbalke/11b92d5ec27afd7a78388db1def3eb76/execroot/__main__/bazel-out/k8-fastbuild/bin/foo
__main__  MANIFEST
$ bazel run :foo.appimage
INFO: Analyzed target //:foo.appimage (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:foo.appimage up-to-date:
  bazel-bin/foo.appimage
INFO: Elapsed time: 0.261s, Critical Path: 0.01s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/foo.appimage
/tmp/.mount_foo.apkjDcjn/foo.runfiles/__main__/foo
ls: cannot access '/tmp/.mount_foo.apkjDcjn/foo.runfiles/__main__/foo.runfiles': No such file or directory

One possible workaround would be to provide RUNFILES_DIR or RUNFILES_MANIFEST_FILE in the environment within a generated AppImage. I have patched around this issue by modifying the runfiles.bash sourcing incantation as follows:

# --- begin runfiles.bash initialization ---
  # Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
  set -euo pipefail
  if [[ ! -d ${RUNFILES_DIR:-/dev/null} && ! -f ${RUNFILES_MANIFEST_FILE:-/dev/null} ]]; then
    if [[ -f "$0.runfiles_manifest" ]]; then
      export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
    elif [[ -f "$0.runfiles/MANIFEST" ]]; then
      export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
    elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
      export RUNFILES_DIR="$0.runfiles"
    elif [[ "$0" == *"runfiles"* ]]; then
      # Handle case where we're executing from within an AppImage. The AppImage
      # invokes as an entry point the symlink to the binary located within the
      # runfiles directory, instead of the binary in the `bazel-out` dir located
      # alongside the runfiles directory. As a result, we need to search up for
      # the directory in $0 instead of next to it.
      export RUNFILES_DIR="${0%runfiles*}runfiles"
    fi
  fi
  if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
    # shellcheck disable=SC1090
    source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
  elif [[ -f ${RUNFILES_MANIFEST_FILE:-/dev/null} ]]; then
    # shellcheck disable=SC1090
    source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
      "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
  elif [[ -f ../bazel_tools/tools/bash/runfiles/runfiles.bash ]]; then
    export RUNFILES_DIR="$(realpath "$(pwd)/..")"
    source ../bazel_tools/tools/bash/runfiles/runfiles.bash
  else
    echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
    exit 1
  fi
  echo $0
  # --- end runfiles.bash initialization ---

appimage rule fails on directory dependencies

If the binary one is trying to build an appimage from contains a directory dependency, for example a filegroup, the build will fail with:

...line 264, in copyfile
    with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
IsADirectoryError: [Errno 21] Is a directory: <directory target path>

'RunEnvironmentInfo' is not defined in Bazel < 5.3

❯ USE_BAZEL_VERSION=5.2.0 bazel test //...
ERROR: /home/laurenz/Documents/Github/rules_appimage/appimage/appimage.bzl:42:9: name 'RunEnvironmentInfo' is not defined
ERROR: error loading package 'tests': compilation of module 'appimage/appimage.bzl' failed
INFO: Elapsed time: 1.480s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (2 packages loaded)
FAILED: Build did NOT complete successfully (2 packages loaded)
    currently loading: tests

Directory behavior incorrectly handles symlinks

Instead of copying the sourcefile of a symlink, the make_appimage method is instead preserving the symlink as-is, resulting in invalid bazel cache symlinks being copied into the AppImage instead of the real source file.

In this line of code symlink should be set to False instead of True.

See the docs for shutil.copytree here for how this flag is handled.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.