Skip to content
Open
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
9 changes: 9 additions & 0 deletions .github/.cspell/project-dictionary.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
atos
codesign
coreutils
debuglink
dsymutil
dwarfdump
lipo
objcopy
otool
segname
vmaddr
xcrun
107 changes: 106 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
bin-leading-dir: bin
debug-archive: true
leading-dir: true
- os: ubuntu-24.04-arm
- os: ubuntu-24.04
target: aarch64-unknown-linux-gnu
Expand All @@ -66,6 +69,7 @@ jobs:
workspace: true
- os: macos-latest
checksums: sha256 sha512 sha1 md5
debug-archive: true
target: aarch64-apple-darwin
- os: macos-latest
checksums: sha256, sha512, sha1, md5
Expand Down Expand Up @@ -93,7 +97,19 @@ jobs:
target: ${{ matrix.target }}
runner: none
if: matrix.target != '' && matrix.build-tool == 'cargo'
- run: cargo new --bin test-crate
- run: |
cargo new --bin test-crate
cat >| test-crate/src/main.rs <<'EOF'
#[unsafe(no_mangle)]
#[inline(never)]
pub extern "C" fn debug_archive_test() {
std::hint::black_box(());
}

fn main() {
debug_archive_test();
}
EOF
- uses: ./
id: upload-rust-binary-action
with:
Expand All @@ -110,6 +126,9 @@ jobs:
tar-xz: all
zip: all
manifest-path: test-crate/Cargo.toml
debug-archive: ${{ matrix.debug-archive || 'false' }}
leading-dir: ${{ matrix.leading-dir || 'false' }}
bin-leading-dir: ${{ matrix.bin-leading-dir }}
codesign: '-'
codesign-prefix: 'com.example.'
codesign-options: 'runtime'
Expand Down Expand Up @@ -138,15 +157,101 @@ jobs:

printf 'outputs.md5 should be a file\n'
test -f "${OUTPUT_MD5}"

if [[ "${DEBUG_ARCHIVE}" == "true" ]]; then
printf 'outputs.debug-archive should be a file\n'
test -f "${OUTPUT_DEBUG_ARCHIVE}"
grep "$(basename -- "${OUTPUT_DEBUG_ARCHIVE}")" "${OUTPUT_SHA256}" >/dev/null
fi
env:
OUTPUT_ARCHIVE: ${{ steps.upload-rust-binary-action.outputs.archive }}
OUTPUT_ZIP: ${{ steps.upload-rust-binary-action.outputs.zip }}
OUTPUT_TAR: ${{ steps.upload-rust-binary-action.outputs.tar }}
OUTPUT_TAR_XZ: ${{ steps.upload-rust-binary-action.outputs.tar-xz }}
OUTPUT_DEBUG_ARCHIVE: ${{ steps.upload-rust-binary-action.outputs.debug-archive }}
OUTPUT_SHA256: ${{ steps.upload-rust-binary-action.outputs.sha256 }}
OUTPUT_SHA512: ${{ steps.upload-rust-binary-action.outputs.sha512 }}
OUTPUT_SHA1: ${{ steps.upload-rust-binary-action.outputs.sha1 }}
OUTPUT_MD5: ${{ steps.upload-rust-binary-action.outputs.md5 }}
DEBUG_ARCHIVE: ${{ matrix.debug-archive || 'false' }}
- name: Check debug archive
if: ${{ matrix.debug-archive }}
env:
BIN_LEADING_DIR: ${{ matrix.bin-leading-dir }}
LEADING_DIR: ${{ matrix.leading-dir || 'false' }}
OUTPUT_ARCHIVE: ${{ steps.upload-rust-binary-action.outputs.archive }}
OUTPUT_DEBUG_ARCHIVE: ${{ steps.upload-rust-binary-action.outputs.debug-archive }}
OUTPUT_TAR: ${{ steps.upload-rust-binary-action.outputs.tar }}
run: |
mkdir -- extracted
tar -xf "${OUTPUT_TAR}" -C extracted
tar -xf "${OUTPUT_DEBUG_ARCHIVE}" -C extracted
debug_prefix=''
if [[ "${LEADING_DIR}" == "true" ]]; then
debug_prefix="${OUTPUT_ARCHIVE}/"
fi
if [[ -n "${BIN_LEADING_DIR}" ]]; then
debug_prefix+="${BIN_LEADING_DIR}/"
fi
binary="extracted/${debug_prefix}test-crate"

case "${RUNNER_OS}" in
Linux)
debug="extracted/${debug_prefix}test-crate.debug"
test -f "${debug}"
binary_sections="$(objdump -h "${binary}")"
debug_sections="$(objdump -h "${debug}")"
if grep '\.debug_info' <<<"${binary_sections}" >/dev/null; then
exit 1
fi
grep '\.debug_info' <<<"${debug_sections}" >/dev/null
readelf --string-dump=.gnu_debuglink "${binary}" |
grep 'test-crate.debug' >/dev/null
address="$(
nm --defined-only "${binary}" |
awk '$3 == "debug_archive_test" { address = $1 }
END { print address }'
)"
test -n "${address}"
addr2line -e "${binary}" "0x${address}" |
grep '/test-crate/src/main.rs:' >/dev/null
;;
macOS)
debug="extracted/${debug_prefix}test-crate.dSYM"
test -d "${debug}"
binary_uuid="$(xcrun dwarfdump --uuid "${binary}" | awk '{ print $2 }')"
debug_uuid="$(xcrun dwarfdump --uuid "${debug}" | awk '{ print $2 }')"
test -n "${binary_uuid}"
test "${binary_uuid}" = "${debug_uuid}"
load_commands="$(otool -l "${binary}")"
if grep '__DWARF' <<<"${load_commands}" >/dev/null; then
exit 1
fi
load_address="$(
awk '
$1 == "segname" && $2 == "__TEXT" { text = 1; next }
text && $1 == "vmaddr" && !address { address = $2 }
END { print address }
' <<<"${load_commands}"
)"
address="$(
nm -nm "${binary}" |
awk '$4 == "_debug_archive_test" { address = $1 }
END { print address }'
)"
dwarf=("${debug}/Contents/Resources/DWARF/"*)
test -n "${load_address}"
test -n "${address}"
test "${#dwarf[@]}" -eq 1
xcrun atos \
-arch arm64 \
-o "${dwarf[0]}" \
-l "${load_address}" \
"0x${address}" |
grep 'main.rs:' >/dev/null
;;
*) exit 1 ;;
esac
- name: Check b2 output
if: ${{ contains(matrix.checksums || 'b2,sha256,sha512,sha1,md5', 'b2') }}
run: |
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Note: In this file, do not use the hard wrap in the middle of a sentence for com

## [Unreleased]

- Add `debug-archive` support for publishing companion debug archives.

## [1.30.2] - 2026-04-17

- Enhance security when `dry-run` is true.
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Currently, this action is basically intended to be used in combination with an a
| checksum | | Algorithms to be used for checksum (sha256, sha512, b2, sha1, or md5) (whitespace or comma separated list).<br>Note: b2 is not available by default on macOS, install `b2sum` to use it. sha1 and md5 are insecure and strongly discouraged. | String | |
| include | | Additional files to be included to the archive (whitespace or comma separated list) | String | |
| asset | | Additional files to be uploaded separately (whitespace or comma separated list) | String | |
| debug-archive | | Preserve debug information in a separate release archive | Boolean | `false` |
| objcopy | | `objcopy` command to use when separating ELF debug information | String | |
| leading-dir | | Whether to create the leading directory in the archive or not | Boolean | `false` |
| bin-leading-dir | | Create extra leading directory(s) for binary file(s) specified by `bin` option | String | |
| build-tool | | Tool to build binaries (cargo, cross, or cargo-zigbuild, see [cross-compilation example](#example-workflow-cross-compilation) for more) | String | |
Expand All @@ -76,6 +78,7 @@ Currently, this action is basically intended to be used in combination with an a
| zip | `.zip` archive file name. |
| tar | `.tar.gz` archive file name. |
| tar-xz | `.tar.xz` archive file name. |
| debug-archive | Separate debug archive file name. |
| sha256 | SHA256 checksum file name. |
| sha512 | SHA512 checksum file name. |
| b2 | BLAKE2 checksum file name. |
Expand Down Expand Up @@ -641,6 +644,25 @@ README.md
- [cargo-hack](https://github.com/taiki-e/cargo-hack/blob/202e6e59d491c9202ce148c9ef423853267226db/.github/workflows/release.yml#L47-L84)
- [tokio-console](https://github.com/tokio-rs/console/blob/9699300ec7901b71dce0d3555a7be2c86ec4e533/.github/workflows/release.yaml#L28-L43)

### Preserve debug information

Use `debug-archive` to publish a small production binary together with a
separate archive containing its debug information:

```yaml
- uses: taiki-e/upload-rust-binary-action@v1
with:
bin: my-cli
debug-archive: true
```

For macOS Darwin targets this publishes `<archive>.dSYM.tar.gz`. For Linux
targets it publishes `<archive>.debug.tar.gz` and adds a GNU debug link to the
binary. The debug archive mirrors the binary archive's leading-directory
layout. Cargo cross builds can specify the target-compatible tool with the
`objcopy` input. The `cross` build tool and universal Darwin targets are not
currently supported by this option. This option requires Rust 1.65 or newer.

### Optimize Rust binary

You can optimize performance or size of Rust binaries by passing the profile options.
Expand Down
15 changes: 15 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ inputs:
Additional files to be uploaded separately (whitespace or comma separated list).
Note that glob pattern is not supported yet.
required: false
debug-archive:
description: Preserve debug information in a separate release archive
required: false
debug_archive:
description: Alias for 'debug-archive'
required: false
default: 'false'
objcopy:
description: objcopy command to use when separating ELF debug information
required: false
leading-dir:
description: Whether to create the leading directory in the archive or not
required: false
Expand Down Expand Up @@ -151,6 +161,9 @@ outputs:
tar-xz:
description: '.tar.xz archive file name'
value: ${{ steps.upload-rust-binary-action.outputs.tar-xz }}
debug-archive:
description: 'Separate debug archive file name'
value: ${{ steps.upload-rust-binary-action.outputs.debug-archive }}
b2:
description: 'BLAKE2 checksum file name'
value: ${{ steps.upload-rust-binary-action.outputs.b2 }}
Expand Down Expand Up @@ -192,6 +205,8 @@ runs:
INPUT_ZIP: ${{ inputs.zip }}
INPUT_INCLUDE: ${{ inputs.include }}
INPUT_ASSET: ${{ inputs.asset }}
INPUT_DEBUG_ARCHIVE: ${{ inputs.debug-archive || inputs.debug_archive }}
INPUT_OBJCOPY: ${{ inputs.objcopy }}
INPUT_LEADING_DIR: ${{ inputs.leading-dir || inputs.leading_dir }}
INPUT_BIN_LEADING_DIR: ${{ inputs.bin-leading-dir }}
INPUT_BUILD_TOOL: ${{ inputs.build-tool || inputs.build_tool }}
Expand Down
120 changes: 120 additions & 0 deletions main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ if [[ -n "${asset}" ]]; then
done < <(normalize_comma_or_space_separated "${asset}")
fi

debug_archive_enabled="${INPUT_DEBUG_ARCHIVE:-}"
case "${debug_archive_enabled}" in
true) debug_archive_enabled=1 ;;
false) debug_archive_enabled='' ;;
*) bail "'debug-archive' input option must be 'true' or 'false': '${debug_archive_enabled}'" ;;
esac

checksum="${INPUT_CHECKSUM:-}"
checksums=()
if [[ -n "${checksum}" ]]; then
Expand Down Expand Up @@ -322,6 +329,39 @@ case "${input_profile}" in
*) profile_directory=${input_profile} ;;
esac

if [[ -n "${debug_archive_enabled}" ]]; then
if [[ "${rustc_minor_version}" -lt 65 ]]; then
bail "'debug-archive' requires Rust 1.65 or newer"
fi
profile_env=$(tr '[:lower:]' '[:upper:]' <<<"${input_profile}")
profile_env=${profile_env//-/_}
export "CARGO_PROFILE_${profile_env}_DEBUG=2"
case "${target}" in
universal-apple-darwin | universal2-apple-darwin)
bail "'debug-archive' does not support universal Darwin targets"
;;
*-apple-darwin)
if [[ "${host_os}" != "macos" ]]; then
bail "'debug-archive' for Darwin targets requires a macOS host"
fi
dsymutil=$(xcrun --find dsymutil)
dsymutil_dir=$(mktemp -d)
ln -s -- "${dsymutil}" "${dsymutil_dir}/dsymutil"
PATH="${dsymutil_dir}:${PATH}"
export PATH
export "CARGO_PROFILE_${profile_env}_SPLIT_DEBUGINFO=packed"
;;
*-linux-*)
export "CARGO_PROFILE_${profile_env}_STRIP=none"
export "CARGO_PROFILE_${profile_env}_SPLIT_DEBUGINFO=off"
;;
*) bail "'debug-archive' does not support target '${target}'" ;;
esac
if [[ "${build_tool}" == "cross" ]]; then
bail "'debug-archive' does not support the cross build tool"
fi
fi

bins=()
for bin_name in "${bin_names[@]}"; do
bins+=("${bin_name}${exe}")
Expand Down Expand Up @@ -453,6 +493,52 @@ case "${INPUT_TARGET:-}" in
;;
esac

debug_files=()
debug_archive=''
if [[ -n "${debug_archive_enabled}" ]]; then
case "${target}" in
*-apple-darwin)
for bin_exe in "${bins[@]}"; do
debug="${target_dir}/${bin_exe}.dSYM"
if [[ ! -d "${debug}" ]]; then
bail "debug bundle not found: ${debug}"
fi
binary_uuid=$(xcrun dwarfdump --uuid "${target_dir}/${bin_exe}" | awk '{ print $2 }')
debug_uuid=$(xcrun dwarfdump --uuid "${debug}" | awk '{ print $2 }')
if [[ -z "${binary_uuid}" ]] || [[ "${binary_uuid}" != "${debug_uuid}" ]]; then
bail "debug bundle UUID does not match binary: ${debug}"
fi
debug_files+=("${bin_exe}.dSYM")
done
debug_archive="${archive}.dSYM.tar.gz"
;;
*)
objcopy="${INPUT_OBJCOPY:-"${OBJCOPY:-}"}"
if [[ -z "${objcopy}" ]]; then
if type -P llvm-objcopy >/dev/null; then
objcopy=llvm-objcopy
elif type -P objcopy >/dev/null; then
objcopy=objcopy
else
bail "'debug-archive' requires objcopy; specify the 'objcopy' input option"
fi
elif ! type -P "${objcopy}" >/dev/null; then
bail "objcopy command not found: ${objcopy}"
fi
for bin_exe in "${bins[@]}"; do
debug="${target_dir}/${bin_exe}.debug"
x "${objcopy}" --only-keep-debug "${target_dir}/${bin_exe}" "${debug}"
(
cd -- "${target_dir}"
x "${objcopy}" --strip-debug --add-gnu-debuglink="${bin_exe}.debug" "${bin_exe}"
)
debug_files+=("${bin_exe}.debug")
done
debug_archive="${archive}.debug.tar.gz"
;;
esac
fi

case "${host_os}" in
macos)
if type -P codesign >/dev/null; then
Expand All @@ -461,6 +547,40 @@ case "${host_os}" in
;;
esac

if [[ -n "${debug_archive}" ]]; then
assets+=("${debug_archive}")
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
printf 'debug-archive=%s\n' "${debug_archive}" >>"${GITHUB_OUTPUT}"
else
warn "GITHUB_OUTPUT is not set; skip setting the 'debug-archive' output"
printf 'debug-archive: %s\n' "${debug_archive}"
fi
debug_archive_path="${PWD}/${debug_archive}"
debug_tmpdir=$(mktemp -d)
mkdir -- "${debug_tmpdir:?}/${archive}"
if [[ -n "${bin_leading_dir}" ]]; then
mkdir -p -- "${debug_tmpdir}/${archive}/${bin_leading_dir}"/
debug_filenames=("${bin_leading_dir%%/*}")
debug_destination="${debug_tmpdir}/${archive}/${bin_leading_dir}"
else
debug_filenames=("${debug_files[@]}")
debug_destination="${debug_tmpdir}/${archive}"
fi
for debug_file in "${debug_files[@]}"; do
x cp -rL -- "${target_dir}/${debug_file}" "${debug_destination}"/
done
pushd -- "${debug_tmpdir}" >/dev/null
if [[ -n "${leading_dir}" ]]; then
x tar acf "${debug_archive_path}" "${archive}"
else
pushd -- "${archive}" >/dev/null
x tar acf "${debug_archive_path}" "${debug_filenames[@]}"
popd >/dev/null
fi
popd >/dev/null
rm -rf -- "${debug_tmpdir:?}"
fi

if [[ "${INPUT_TAR/all/${platform}}" == "${platform}" ]] \
|| [[ "${INPUT_TAR_XZ/all/${platform}}" == "${platform}" ]] \
|| [[ "${INPUT_ZIP/all/${platform}}" == "${platform}" ]]; then
Expand Down
Loading