Skip to content

Commit

Permalink
Set BUILD_WORKING_DIRECTORY also during APPIMAGE_EXTRACT_AND_RUN
Browse files Browse the repository at this point in the history
  • Loading branch information
lalten committed Apr 11, 2024
1 parent 3146b4a commit 98e25a7
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 29 deletions.
20 changes: 13 additions & 7 deletions appimage/appimage.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,22 @@ def _appimage_impl(ctx):
env.update(ctx.attr.env)

# Export the current environment to a file so that it can be re-sourced in AppRun
cmd = " | ".join([
# Export the current environment, which is a combination of the build env and the user-provided env
"export -p",
# Some shells like to use `declare -x` instead of `export`. The build time shell isn't necessarily the same as
# the runtime shell, so there is no guarantee that `declare` is available at runtime. Let's use `export` instead.
"sed 's/^declare -x/export/'",
# Some build-time values are not interesting or even incorrect at AppRun runtime
"grep -v '^export OLDPWD$$'",
"grep -v '^export PWD='",
"grep -v '^export SHLVL='",
"grep -v '^export TMPDIR='",
]) + " > " + env_file.path
ctx.actions.run_shell(
outputs = [env_file],
env = env,
command = "".join([
"export -p",
# Some shells like to use `declare -x` instead of `export`. However there is no guarantee that `declare` is
# available at runtime. So let's use `export` instead.
" | sed 's/^declare -x/export/'",
" > " + env_file.path,
]),
command = cmd,
)

# Run our tool to create the AppImage
Expand Down
70 changes: 48 additions & 22 deletions appimage/private/tool/mkappimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,53 @@ def _move_relative_symlinks_in_files_to_their_own_section(manifest_data: _Manife
return new_manifest_data


def _make_apprun_content(params: AppDirParams) -> str:
"""Generate the AppRun, which is the AppImage's entrypoint."""
# A shebang with `/bin/sh` does not mean a specific shell but any POSIX standard compliant shell.
apprun_lines = ["#!/bin/sh"]

# Set up the previously `export -p`ed environment.
# This sets up any environment variables coming from the `env` attribute on the appimage target.
apprun_lines.extend(params.envfile.read_text().splitlines())

# The generated AppImage must be able to run outside of Bazel. We conveniently set BUILD_WORKING_DIRECTORY in the
# to the same value that it would have if the code would be run under `bazel run`. This is important for calculating
# the actual location of relative paths passed in as user input on command line arguments.
# We set BUILD_WORKING_DIRECTORY to the first set and not empty value of [BUILD_WORKING_DIRECTORY, OWD, PWD].
# * $BUILD_WORKING_DIRECTORY (see https://bazel.build/docs/user-manual#running-executables) is set by Bazel in
# https://github.com/bazelbuild/bazel/blob/7.1.1/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java#L548
# and is only available when Bazel executes the AppImage for us during bazel run (but not test/coverage).
# * $OWD ("Original Working Directory") is set by the AppImage runtime in
# https://github.com/lalten/type2-runtime/blob/84f7a00/src/runtime/runtime.c#L1757.
# When the AppImage is mounted with libfuse its working directory is inside the mount point, which is not the
# original working directory of the user. Presumably this is why the AppImage runtime sets OWD only when mounted
# with libfuse but *not* when running with APPIMAGE_EXTRACT_AND_RUN=1 or --appimage-extract-and-run. See
# https://github.com/AppImage/type2-runtime/issues/23).
# * $PWD is set by the shell to the current working directory. This is correct if and only if we are not running
# under Bazel and not mounted with libfuse, so it is a good value to use as a fallback. It's important to store
# $PWD to $BUILD_WORKING_DIRECTORY because we change the working directory to the $RUNFILES_DIR below. This means
# that at runtime, $PWD is different to the directory that the user ran the appimage from.
# This is done with POSIX shell command language ${parameter:-word} "Use Default Values" parameter expansion, see
# https://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
# Note that BUILD_WORKING_DIRECTORY's sibling BUILD_WORKSPACE_DIRECTORY is not of much use here as even if we knew
# it at build time, it's not guaranteed to be correct or available at runtime.
apprun_lines.append('OWD="${OWD=$PWD}"') # remove when https://github.com/AppImage/type2-runtime/issues/23 is fixed
apprun_lines.append('BUILD_WORKING_DIRECTORY="${BUILD_WORKING_DIRECTORY=$OWD}"')
apprun_lines.append("export BUILD_WORKING_DIRECTORY")

# Explicitly set RUNFILES_DIR to the runfiles dir of the binary instead of the appimage rule itself
apprun_lines.append(f'workdir="$(dirname "$0")/{params.workdir}"')
apprun_lines.append('RUNFILES_DIR="$(dirname "$workdir")"')
apprun_lines.append("export RUNFILES_DIR")
# Run under runfiles
apprun_lines.append('cd "$workdir"')

# Launch the actual binary
apprun_lines.append(f'exec "./{params.entrypoint}" "$@"')

return "\n".join(apprun_lines) + "\n"


def populate_appdir(appdir: Path, params: AppDirParams) -> None:
"""Make the AppDir that will be squashfs'd into the AppImage."""
appdir.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -208,28 +255,7 @@ def populate_appdir(appdir: Path, params: AppDirParams) -> None:
linkfile.symlink_to(target)

apprun_path = appdir / "AppRun"
apprun_path.write_text(
"\n".join(
[
"#!/bin/sh",
# Set up the previously `export -p`ed environment
*params.envfile.read_text().splitlines(),
# If running as AppImage outside Bazel, conveniently set BUILD_WORKING_DIRECTORY, like `bazel run` would
# `$OWD` ("Original Working Directory") is set by the AppImage runtime in
# https://github.com/lalten/type2-runtime/blob/84f7a00/src/runtime/runtime.c#L1757
# Note that we can not set BUILD_WORKSPACE_DIRECTORY as it's not known when the AppImage is deployed
'[ "${BUILD_WORKING_DIRECTORY+1}" ] || export BUILD_WORKING_DIRECTORY="$OWD"',
# Explicitly set RUNFILES_DIR to the runfiles dir of the binary instead of the appimage rule itself
f'workdir="$(dirname $0)/{params.workdir}"',
'export RUNFILES_DIR="$(dirname "${workdir}")"',
# Run under runfiles
'cd "${workdir}"',
# Launch the actual binary
f'exec "./{params.entrypoint}" "$@"',
],
)
+ "\n",
)
apprun_path.write_text(_make_apprun_content(params))
apprun_path.chmod(0o751)

apprun_path.with_suffix(".desktop").write_text(
Expand Down
19 changes: 19 additions & 0 deletions tests/build_working_directory/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@rules_appimage//appimage:appimage.bzl", "appimage")

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

appimage(
name = "test.appimage",
binary = ":entrypoint",
)

sh_test(
name = "test",
timeout = "short",
srcs = ["test.sh"],
data = [":test.appimage"],
tags = ["requires-fakeroot"],
)
2 changes: 2 additions & 0 deletions tests/build_working_directory/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
env | sort
19 changes: 19 additions & 0 deletions tests/build_working_directory/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
set -euxo pipefail

actual_wd="$(pwd)"

# BUILD_WORKING_DIRECTORY is not set when running the appimage directly
unset BUILD_WORKING_DIRECTORY

# BUILD_WORKING_DIRECTORY is set inside the appimage when libfuse-mounted
env="$(env -i -- PATH="/bin" tests/build_working_directory/test.appimage)"
grep -q OWD= <<<"$env"
bwd="$(grep BUILD_WORKING_DIRECTORY= <<<"$env" | cut -d= -f2-)"
[ "$bwd" = "$actual_wd" ]

# BUILD_WORKING_DIRECTORY is set inside the appimage when using APPIMAGE_EXTRACT_AND_RUN
env="$(env -i -- PATH="/bin" APPIMAGE_EXTRACT_AND_RUN=1 tests/build_working_directory/test.appimage)"
grep -q OWD= <<<"$env" && exit 1 # https://github.com/AppImage/type2-runtime/issues/23
bwd="$(grep BUILD_WORKING_DIRECTORY= <<<"$env" | cut -d= -f2-)"
[ "$bwd" = "$actual_wd" ]

0 comments on commit 98e25a7

Please sign in to comment.