Skip to content

Commit 98c83eb

Browse files
authored
Fix Procmon ARM64 Debian package architecture (#160)
Derive Debian metadata from the CMake target and use the configured architecture in package filenames. Propagate packaging failures and add regression tests.
1 parent 4c5e752 commit 98c83eb

5 files changed

Lines changed: 132 additions & 13 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ configure_file ("${PROJECT_SOURCE_DIR}/src/version.h.in"
137137
"${PROJECT_BINARY_DIR}/src/version.h"
138138
)
139139

140+
include(cmake/PackageArchitecture.cmake)
140141
configure_file(dist/DEBIAN.in/control.in DEBIANcontrol)
141142
configure_file(dist/SPECS.in/spec.in SPECS.spec)
142143

‎cmake/PackageArchitecture.cmake‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Use the compiler target, not the build host, for Debian package metadata.
2+
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" PACKAGE_PROCESSOR)
3+
if(PACKAGE_PROCESSOR MATCHES "^(x86_64|amd64)$")
4+
set(DEB_ARCH "amd64")
5+
elseif(PACKAGE_PROCESSOR MATCHES "^(aarch64|arm64)$")
6+
set(DEB_ARCH "arm64")
7+
else()
8+
message(FATAL_ERROR "Unsupported package architecture: ${CMAKE_SYSTEM_PROCESSOR}")
9+
endif()

‎dist/DEBIAN.in/control.in‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: procmon
22
Version: @PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@
3-
Architecture: amd64
3+
Architecture: @DEB_ARCH@
44
Maintainer: Sysinternals <syssite@microsoft.com>
55
Description: Sysinternals process monitor utility
66
Process Monitor (Procmon) is part of the Sysinternals suite of tools. Procmon provides a convenient and efficient way for Linux developers to trace the syscall

‎makePackages.sh‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,21 @@ PACKAGE_VER=$4
3737
PACKAGE_REL=$5
3838
PACKAGE_TYPE=$6
3939

40-
DEB_PACKAGE_NAME="${PACKAGE_NAME}_${PACKAGE_VER}_amd64"
4140
RPM_PACKAGE_NAME="${PACKAGE_NAME}-${PACKAGE_VER}-${PACKAGE_REL}"
4241

4342
if [ "$PACKAGE_TYPE" = "deb" ]; then
44-
DPKGDEB=`which dpkg-deb`
43+
# Fail on staging errors and propagate dpkg-deb failures to the build job.
44+
set -e
45+
DPKGDEB=$(command -v dpkg-deb) || { echo "No dpkg-deb found" >&2; exit 1; }
46+
47+
# CMake configures the target architecture once in DEBIANcontrol. Use it
48+
# for the filename too, including when cross-compiling on an x64 host.
49+
DEB_ARCH=$(sed -n 's/^Architecture: *//p' "${PROJECT_BINARY_DIR}/DEBIANcontrol" | tr -d '\r')
50+
case "$DEB_ARCH" in
51+
amd64|arm64) ;;
52+
*) echo "Unsupported or missing Debian architecture: $DEB_ARCH" >&2; exit 1 ;;
53+
esac
54+
DEB_PACKAGE_NAME="${PACKAGE_NAME}_${PACKAGE_VER}_${DEB_ARCH}"
4555

4656
if [ -d "${PROJECT_BINARY_DIR}/deb" ]; then
4757
rm -rf "${PROJECT_BINARY_DIR}/deb"
@@ -60,16 +70,9 @@ if [ "$PACKAGE_TYPE" = "deb" ]; then
6070
cp "${PROJECT_BINARY_DIR}/procmon" "${PROJECT_BINARY_DIR}/deb/${DEB_PACKAGE_NAME}/usr/bin/"
6171

6272
# make the deb
63-
if [ "$DPKGDEB" != "" ]; then
64-
cd "${PROJECT_BINARY_DIR}/deb"
65-
"$DPKGDEB" -Zxz --build --root-owner-group "${DEB_PACKAGE_NAME}"
66-
RET=$?
67-
else
68-
echo "No dpkg-deb found"
69-
RET=1
70-
fi
71-
72-
exit 0
73+
cd "${PROJECT_BINARY_DIR}/deb"
74+
"$DPKGDEB" -Zxz --build --root-owner-group "${DEB_PACKAGE_NAME}"
75+
exit $?
7376
fi
7477

7578
if [ "$PACKAGE_TYPE" = "rpm" ]; then

‎tests/packaging.test.cjs‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Run with: node --test tests/packaging.test.cjs
2+
// Requires CMake and Bash (override CMAKE_PATH and BASH_PATH on Windows).
3+
const assert = require('node:assert/strict');
4+
const fs = require('node:fs');
5+
const os = require('node:os');
6+
const path = require('node:path');
7+
const { spawnSync } = require('node:child_process');
8+
const { test } = require('node:test');
9+
10+
const source = path.resolve(__dirname, '..');
11+
const cmake = process.env.CMAKE_PATH || 'cmake';
12+
const bash = process.env.BASH_PATH || 'bash';
13+
const posix = (file) => file.replaceAll('\\', '/').replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`);
14+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
15+
function fixture(run) {
16+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'procmon packaging '));
17+
try { return run(directory); } finally { fs.rmSync(directory, { recursive: true, force: true }); }
18+
}
19+
function command(executable, args, directory, env = {}) {
20+
const result = spawnSync(executable, args, {
21+
cwd: directory, env: { ...process.env, ...env }, encoding: 'utf8', timeout: 30000,
22+
});
23+
if (result.error) throw result.error;
24+
return result;
25+
}
26+
function passed(result) { assert.equal(result.status, 0, result.stdout + result.stderr); }
27+
28+
function configure(directory, processor) {
29+
const script = path.join(directory, 'configure.cmake');
30+
fs.writeFileSync(script, `
31+
include("${source.replaceAll('\\', '/')}/cmake/PackageArchitecture.cmake")
32+
set(PROJECT_VERSION_MAJOR 2)
33+
set(PROJECT_VERSION_MINOR 2)
34+
set(PROJECT_VERSION_PATCH 2)
35+
configure_file("${source.replaceAll('\\', '/')}/dist/DEBIAN.in/control.in" DEBIANcontrol @ONLY)
36+
`);
37+
return command(cmake, [`-DCMAKE_SYSTEM_PROCESSOR=${processor}`, '-P', script], directory);
38+
}
39+
40+
function packageDeb(directory, failure = 0) {
41+
const script = path.join(directory, 'build-package.sh');
42+
fs.writeFileSync(script, `
43+
set -e
44+
dpkg-deb() {
45+
if [ "$MOCK_FAILURE" != 0 ]; then return "$MOCK_FAILURE"; fi
46+
for argument in "$@"; do package="$argument"; done
47+
test -f "$package/DEBIAN/control"
48+
test -f "$package/usr/bin/procmon"
49+
touch "$package.deb"
50+
}
51+
# Source in a POSIX-mode shell so dpkg-deb can be mocked without downloading
52+
# or running a Linux package builder. All staging commands execute normally.
53+
set -- ${quote(posix(source))} ${quote(posix(directory))} procmon 2.2.2 0 deb
54+
. ${quote(posix(path.join(source, 'makePackages.sh')))}
55+
`);
56+
return command(bash, ['--posix', posix(script)], directory, { MOCK_FAILURE: String(failure) });
57+
}
58+
59+
function stageInputs(directory) {
60+
for (const file of ['procmon', 'procmon.1.gz', 'changelog.gz']) {
61+
fs.writeFileSync(path.join(directory, file), 'fixture');
62+
}
63+
}
64+
65+
for (const [processor, architecture] of [['x86_64', 'amd64'], ['AMD64', 'amd64'], ['aarch64', 'arm64'], ['arm64', 'arm64']]) {
66+
test(`${processor}: CMake target controls both Debian metadata and filename`, () => fixture((directory) => {
67+
passed(configure(directory, processor));
68+
stageInputs(directory);
69+
passed(packageDeb(directory));
70+
const name = `procmon_2.2.2_${architecture}`;
71+
assert.ok(fs.existsSync(path.join(directory, 'deb', `${name}.deb`)));
72+
const control = fs.readFileSync(path.join(directory, 'deb', name, 'DEBIAN', 'control'), 'utf8');
73+
assert.match(control, new RegExp(`^Architecture: ${architecture}$`, 'm'));
74+
assert.doesNotMatch(control, /@DEB_ARCH@/);
75+
assert.equal(fs.readdirSync(path.join(directory, 'deb')).filter((file) => file.endsWith('.deb')).length, 1);
76+
}));
77+
}
78+
79+
test('unsupported CMake target is rejected', () => fixture((directory) => {
80+
const result = configure(directory, 'unknown');
81+
assert.notEqual(result.status, 0);
82+
assert.match(result.stderr, /Unsupported package architecture/);
83+
}));
84+
85+
test('missing or unsupported control architecture is rejected', () => fixture((directory) => {
86+
stageInputs(directory);
87+
for (const control of ['Package: procmon\n', 'Architecture: unknown\n', 'Architecture: @DEB_ARCH@\n']) {
88+
fs.writeFileSync(path.join(directory, 'DEBIANcontrol'), control);
89+
const result = packageDeb(directory);
90+
assert.notEqual(result.status, 0);
91+
assert.match(result.stderr, /Unsupported or missing Debian architecture/);
92+
}
93+
}));
94+
95+
test('dpkg-deb failure reaches the build job', () => fixture((directory) => {
96+
passed(configure(directory, 'aarch64'));
97+
stageInputs(directory);
98+
assert.equal(packageDeb(directory, 17).status, 17);
99+
}));
100+
101+
test('missing binary fails staging instead of packaging an incomplete install', () => fixture((directory) => {
102+
passed(configure(directory, 'aarch64'));
103+
stageInputs(directory);
104+
fs.unlinkSync(path.join(directory, 'procmon'));
105+
assert.notEqual(packageDeb(directory).status, 0);
106+
}));

0 commit comments

Comments
 (0)