Skip to content

Commit 3251fba

Browse files
committed
Fix Procmon ARM64 Debian package architecture
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 3251fba

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)