Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ccpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@ jobs:
bazel test //... --test_output=errors
bazel build //:fluxengine_dmg --config=stamp --test_output=errors

- name: Signing package
run: |
cd fluxengine
sh scripts/resign-dmg.sh bazel-bin/java/com/cowlark/fluxengine/fluxengine.dmg fluxengine-signed.dmg
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}.osx.${{ runner.arch }}
path: |
fluxengine/bazel-bin/java/com/cowlark/fluxengine/*.dmg
fluxengine/fluxengine-signed.dmg

build-windows:
runs-on: windows-latest
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ maven.install(
"com.jayway.jsonpath:json-path:3.0.0",
"junit:junit:4.13.2",
"org.usb4java:usb4java:1.3.0",
"io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1",
"com.formdev:flatlaf:3.7.2",
"com.formdev:flatlaf-swingx:3.7.2",
"org.exbin.bined:bined-core:0.2.2",
Expand Down
22 changes: 10 additions & 12 deletions java/com/cowlark/fluxengine/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,24 @@ java_binary(
jpackage(
name = "fluxengine_deb",
package_name = "fluxengine",
app_version = "1.0.0",
stamp = True,
extra_launchers = [":fluxengine-gui.properties"],
jar = ":fluxengine_deploy.jar",
launcher_icon = "//extras:icon.png",
main_class = "com.cowlark.fluxengine.cli.Main",
package_type = "deb",
stamp = True,
tags = ["manual"],
)

jpackage(
name = "fluxengine_rpm",
package_name = "fluxengine",
app_version = "1.0.0",
stamp = True,
extra_launchers = [":fluxengine-gui.properties"],
jar = ":fluxengine_deploy.jar",
launcher_icon = "//extras:icon.png",
main_class = "com.cowlark.fluxengine.cli.Main",
package_type = "rpm",
stamp = True,
tags = ["manual"],
)

Expand All @@ -46,8 +44,6 @@ jpackage(
jpackage(
name = "fluxengine_msi",
package_name = "fluxengine",
app_version = "1.0.0",
stamp = True,
extra_launchers = [":fluxengine-gui.properties"],
jar = ":fluxengine_deploy.jar",
launcher_icon = select({
Expand All @@ -59,36 +55,38 @@ jpackage(
"@platforms//os:windows": "msi",
"//conditions:default": "unsupported",
}),
stamp = True,
tags = ["manual"],
)

jpackage(
name = "fluxengine_dmg",
package_name = "fluxengine",
app_version = "1.0.0",
stamp = True,
extra_launchers = [":fluxengine-gui.properties"],
extra_jars = ["@maven//:io_github_dsheirer_libusb4java_darwin_aarch64"],
extra_jpackage_args = [
"--mac-package-name FluxEngine",
],
jar = ":fluxengine_deploy.jar",
launcher_icon = select({
"@platforms//os:osx": "//extras:fluxengine_icns",
"//conditions:default": "//extras:icon.png",
}),
main_class = "com.cowlark.fluxengine.cli.Main",
main_class = "com.cowlark.fluxengine.gui.Gui",
package_type = select({
"@platforms//os:osx": "dmg",
"//conditions:default": "unsupported",
}),
stamp = True,
tags = ["manual"],
)

jpackage_app_image(
name = "fluxengine_app_image",
package_name = "fluxengine",
app_version = "1.0.0",
stamp = True,
extra_launchers = [":fluxengine-gui.properties"],
jar = ":fluxengine_deploy.jar",
launcher_icon = "//extras:icon.png",
main_class = "com.cowlark.fluxengine.cli.Main",
stamp = True,
tags = ["manual"],
)
40 changes: 37 additions & 3 deletions scripts/jpackage.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ def _jpackage_impl(ctx):
else:
out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + "." + extension)
launchers = _launcher_properties(ctx)
stage_extra_jars = "\n".join([
(' cp -L "{jar}" workdir/input/\n' +
' (cd workdir/input && "{jar_tool}" xf "{jar}")').format(
jar_tool = java_runtime.java_home + "/bin/jar",
jar = extra_jar.path,
)
for extra_jar in ctx.files.extra_jars
])

# jpackage writes a lot of scratch state (a jlink runtime image and an app
# image) and chmods files in it. Do all the scratch work in a plain
Expand All @@ -91,14 +99,15 @@ def _jpackage_impl(ctx):
stamp_inputs = [ctx.info_file] if ctx.attr.stamp else []
ctx.actions.run_shell(
outputs = [out],
inputs = [jar] + [f for (_, f) in launchers] +\
inputs = [jar] + ctx.files.extra_jars + [f for (_, f) in launchers] +\
([ctx.file.launcher_icon] if ctx.file.launcher_icon else []) + stamp_inputs,
tools = [java_runtime.files],
use_default_shell_env = True,
command = """
rm -rf workdir
mkdir -p workdir/input workdir/tmp workdir/dest workdir/home workdir/rpmbuild workdir/resources
cp -L "{jar}" workdir/input/
{stage_extra_jars}
chmod u+w workdir/input/*
printf '[Desktop Entry]\\nName=FluxEngine\\nComment=FluxEngine\\nExec=APPLICATION_LAUNCHER\\nIcon=APPLICATION_ICON\\nTerminal=false\\nType=Application\\nCategories=DEPLOY_BUNDLE_CATEGORY\\n' > workdir/resources/fluxengine-gui.desktop
if [ "{package_type}" = "rpm" ]; then
Expand Down Expand Up @@ -129,6 +138,7 @@ def _jpackage_impl(ctx):
--main-jar "{main_jar}" \
--main-class "{main_class}" \
$WIN_CONSOLE \
{extra_jpackage_args} \
--resource-dir "$(pwd)/workdir/resources" \
{add_launcher_args} \
--jlink-options "--strip-debug --no-header-files --no-man-pages --strip-native-commands" \
Expand All @@ -145,8 +155,11 @@ def _jpackage_impl(ctx):
info_file = ctx.info_file.path if ctx.attr.stamp else "",
jar = jar.path,
main_jar = jar.basename,
jar_tool = java_runtime.java_home + "/bin/jar",
stage_extra_jars = stage_extra_jars,
main_class = ctx.attr.main_class,
add_launcher_args = _add_launcher_args(launchers),
extra_jpackage_args = " ".join(ctx.attr.extra_jpackage_args),
out = out.path,
),
mnemonic = "Jpackage" + package_type.title(),
Expand All @@ -167,6 +180,14 @@ def _jpackage_app_image_impl(ctx):
else:
out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + ".tar.xz")
launchers = _launcher_properties(ctx)
stage_extra_jars = "\n".join([
(' cp -L "{jar}" workdir/input/\n' +
' (cd workdir/input && "{jar_tool}" xf "{jar}")').format(
jar_tool = java_runtime.java_home + "/bin/jar",
jar = extra_jar.path,
)
for extra_jar in ctx.files.extra_jars
])

# jpackage --type app-image writes a directory (with a jlink runtime image
# and the app launcher) and chmods files in it. Do the scratch work in a
Expand All @@ -180,14 +201,15 @@ def _jpackage_app_image_impl(ctx):
stamp_inputs = [ctx.info_file] if ctx.attr.stamp else []
ctx.actions.run_shell(
outputs = [out],
inputs = [jar] + [f for (_, f) in launchers] +\
inputs = [jar] + ctx.files.extra_jars + [f for (_, f) in launchers] +\
([ctx.file.launcher_icon] if ctx.file.launcher_icon else []) + stamp_inputs,
tools = [java_runtime.files],
use_default_shell_env = True,
command = """
rm -rf workdir
mkdir -p workdir/input workdir/tmp workdir/dest workdir/home workdir/resources
cp -L "{jar}" workdir/input/
{stage_extra_jars}
chmod u+w workdir/input/*
printf '[Desktop Entry]\nName=FluxEngine\nComment=FluxEngine\nExec=APPLICATION_LAUNCHER\nIcon=APPLICATION_ICON\nTerminal=false\nType=Application\nCategories=DEPLOY_BUNDLE_CATEGORY\n' > workdir/resources/fluxengine-gui.desktop
TMPDIR="$(pwd)/workdir/tmp"
Expand All @@ -206,6 +228,7 @@ def _jpackage_app_image_impl(ctx):
--input "$(pwd)/workdir/input" \
--main-jar "{main_jar}" \
--main-class "{main_class}" \
{extra_jpackage_args} \
--resource-dir "$(pwd)/workdir/resources" \
{add_launcher_args} \
--jlink-options "--strip-debug --no-header-files --no-man-pages --strip-native-commands" \
Expand All @@ -224,8 +247,11 @@ def _jpackage_app_image_impl(ctx):
info_file = ctx.info_file.path if ctx.attr.stamp else "",
jar = jar.path,
main_jar = jar.basename,
jar_tool = java_runtime.java_home + "/bin/jar",
stage_extra_jars = stage_extra_jars,
main_class = ctx.attr.main_class,
add_launcher_args = _add_launcher_args(launchers),
extra_jpackage_args = " ".join(ctx.attr.extra_jpackage_args),
out = out.path,
),
mnemonic = "JpackageAppImage",
Expand All @@ -248,6 +274,10 @@ _jpackage_attrs = {
doc = "jpackage launcher properties files; " +
"each launcher is named after the file (minus its .properties suffix).",
),
"extra_jars": attr.label_list(
allow_files = [".jar"],
doc = "Additional runtime JARs copied beside the main application JAR.",
),
"launcher_icon": attr.label(
allow_single_file = [".png", ".ico", ".icns"],
doc = "Icon for all launchers, added to each launcher's " +
Expand All @@ -259,7 +289,7 @@ _jpackage_attrs = {
doc = "The package name; also used for the output filename.",
),
"app_version": attr.string(
mandatory = True,
default = "1.0.0",
doc = "Application version, e.g. '1.0.0'. Used as fallback when stamp is off.",
),
"stamp": attr.bool(
Expand All @@ -272,6 +302,10 @@ _jpackage_attrs = {
doc = "The jpackage package type: deb/rpm (Linux), msi (Windows), dmg (macOS). " +
"Use select() so this is only set to the matching platform.",
),
"extra_jpackage_args": attr.string_list(
default = [],
doc = "Additional arguments to pass to jpackage verbatim.",
),
}

jpackage = rule(
Expand Down
76 changes: 76 additions & 0 deletions scripts/resign-dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
#
# resign-dmg.sh: unpack a dmg, ad-hoc sign the .app inside, and rebuild the dmg.
#
# Usage: ./resign-dmg.sh input.dmg [output.dmg]
# If output.dmg is omitted, the input file is replaced.
#
# Set SIGN_IDENTITY to use a real certificate instead of ad-hoc ("-"):
# SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" ./resign-dmg.sh in.dmg

set -euo pipefail

IN_DMG="${1:?Usage: $0 input.dmg [output.dmg]}"
OUT_DMG="${2:-$IN_DMG}"
IDENTITY="${SIGN_IDENTITY:--}"

[[ -f "$IN_DMG" ]] || { echo "Not found: $IN_DMG" >&2; exit 1; }

WORK="$(mktemp -d)"
MOUNT="$WORK/mount"
STAGE="$WORK/stage"
mkdir -p "$MOUNT" "$STAGE"

cleanup() {
hdiutil detach "$MOUNT" -quiet -force 2>/dev/null || true
rm -rf "$WORK"
}
trap cleanup EXIT

echo "==> Mounting $IN_DMG"
hdiutil attach "$IN_DMG" -mountpoint "$MOUNT" -nobrowse -readonly -quiet

# Reuse the original volume name
VOLNAME="$(diskutil info "$MOUNT" | sed -n 's/^ *Volume Name: *//p')"
VOLNAME="${VOLNAME:-$(basename "$IN_DMG" .dmg)}"

echo "==> Copying contents (volume: $VOLNAME)"
# ditto preserves symlinks (e.g. /Applications), xattrs and hidden files
ditto "$MOUNT" "$STAGE"
hdiutil detach "$MOUNT" -quiet

APP="$(find "$STAGE" -maxdepth 1 -name '*.app' -type d | head -n 1)"
[[ -n "$APP" ]] || { echo "No .app found in dmg" >&2; exit 1; }
echo "==> Found app: $(basename "$APP")"

# Remove any existing (possibly broken) signatures and quarantine flags
xattr -cr "$APP" || true
find "$APP" -name '_CodeSignature' -type d -prune -exec rm -rf {} + 2>/dev/null || true

echo "==> Signing nested binaries (identity: $IDENTITY)"
# Sign every Mach-O file (dylibs, jspawnhelper, java, launcher, ...) inside-out.
# Deepest paths first so nested code is signed before what contains it.
find "$APP" -type f -print0 \
| while IFS= read -r -d '' f; do
if file -b "$f" | grep -q 'Mach-O'; then
printf '%s\n' "$f"
fi
done \
| awk '{ print length($0) "\t" $0 }' | sort -rn | cut -f2- \
| while IFS= read -r f; do
codesign --force --sign "$IDENTITY" --timestamp=none "$f"
done

echo "==> Signing app bundle"
codesign --force --sign "$IDENTITY" --timestamp=none "$APP"

echo "==> Verifying"
codesign --verify --deep --strict --verbose=2 "$APP"
codesign -dvv "$APP" 2>&1 | grep -E 'Signature|Identifier' || true

echo "==> Building dmg: $OUT_DMG"
TMP_DMG="$WORK/out.dmg"
hdiutil create -volname "$VOLNAME" -srcfolder "$STAGE" -ov -format UDZO -quiet "$TMP_DMG"
mv -f "$TMP_DMG" "$OUT_DMG"

echo "Done: $OUT_DMG"
Loading