Skip to content

Commit 77f673a

Browse files
authored
Merge pull request #1201 from PowerShell/copilot/create-rpm-package
Add RPM and DEB package support to build system and CI/CD pipeline
2 parents de79d34 + 1867d99 commit 77f673a

File tree

4 files changed

+246
-7
lines changed

4 files changed

+246
-7
lines changed

.pipelines/DSC-Official.yml

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,17 @@ extends:
236236
ob_restore_phase: true
237237
- pwsh: |
238238
apt update
239-
apt -y install musl-tools
239+
apt -y install musl-tools rpm dpkg build-essential
240240
$header = "Bearer $(AzToken)"
241241
$env:CARGO_REGISTRIES_POWERSHELL_TOKEN = $header
242242
$env:CARGO_REGISTRIES_POWERSHELL_CREDENTIAL_PROVIDER = 'cargo:token'
243243
./build.ps1 -Release -Architecture x86_64-unknown-linux-musl
244244
./build.ps1 -PackageType tgz -Architecture x86_64-unknown-linux-musl -Release
245+
./build.ps1 -PackageType rpm -Architecture x86_64-unknown-linux-musl -Release
246+
./build.ps1 -PackageType deb -Architecture x86_64-unknown-linux-musl -Release
245247
Copy-Item ./bin/*.tar.gz "$(ob_outputDirectory)"
248+
Copy-Item ./bin/*.rpm "$(ob_outputDirectory)"
249+
Copy-Item ./bin/*.deb "$(ob_outputDirectory)"
246250
displayName: 'Build x86_64-unknown-linux-musl'
247251
condition: succeeded()
248252
@@ -289,6 +293,9 @@ extends:
289293
#apt -y install gcc-multilib
290294
apt -y install libssl-dev
291295
apt -y install pkg-config
296+
apt -y install rpm
297+
apt -y install dpkg
298+
apt -y install build-essential
292299
msrustup default stable-aarch64-unknown-linux-musl
293300
if ((openssl version -d) -match 'OPENSSLDIR: "(?<dir>.*?)"') {
294301
$env:OPENSSL_LIB_DIR = $matches['dir']
@@ -298,7 +305,11 @@ extends:
298305
$env:CARGO_REGISTRIES_POWERSHELL_CREDENTIAL_PROVIDER = 'cargo:token'
299306
./build.ps1 -Release -Architecture aarch64-unknown-linux-musl
300307
./build.ps1 -PackageType tgz -Architecture aarch64-unknown-linux-musl -Release
308+
./build.ps1 -PackageType rpm -Architecture aarch64-unknown-linux-musl -Release
309+
./build.ps1 -PackageType deb -Architecture aarch64-unknown-linux-musl -Release
301310
Copy-Item ./bin/*.tar.gz "$(ob_outputDirectory)"
311+
Copy-Item ./bin/*.rpm "$(ob_outputDirectory)"
312+
Copy-Item ./bin/*.deb "$(ob_outputDirectory)"
302313
displayName: 'Build aarch64-unknown-linux-musl'
303314
condition: succeeded()
304315
@@ -370,11 +381,17 @@ extends:
370381

371382
- download: current
372383
artifact: drop_BuildAndSign_BuildLinuxArm64Musl
373-
patterns: '*.tar.gz'
384+
patterns: |
385+
*.tar.gz
386+
*.rpm
387+
*.deb
374388
375389
- download: current
376390
artifact: drop_BuildAndSign_BuildLinuxMusl
377-
patterns: '*.tar.gz'
391+
patterns: |
392+
*.tar.gz
393+
*.rpm
394+
*.deb
378395
379396
- download: current
380397
artifact: release ## this includes artifacts for macOS
@@ -385,7 +402,7 @@ extends:
385402
patterns: '*.msixbundle'
386403

387404
- pwsh: |
388-
Get-ChildItem "$(Pipeline.Workspace)" -Recurse -Include '*.zip', '*.tar.gz', '*.msixbundle' | ForEach-Object {
405+
Get-ChildItem "$(Pipeline.Workspace)" -Recurse -Include '*.zip', '*.tar.gz', '*.msixbundle', '*.rpm', '*.deb' | ForEach-Object {
389406
Write-Host "Found artifact: $($_.FullName)"
390407
}
391408
displayName: List downloaded artifacts
@@ -398,7 +415,7 @@ extends:
398415
399416
Write-Verbose -Verbose "Starting to copy"
400417
401-
Get-ChildItem "$(Pipeline.Workspace)" -Recurse -Include '*.zip', '*.tar.gz', '*.msixbundle' | ForEach-Object {
418+
Get-ChildItem "$(Pipeline.Workspace)" -Recurse -Include '*.zip', '*.tar.gz', '*.msixbundle', '*.rpm', '*.deb' | ForEach-Object {
402419
Copy-Item -Path $_.FullName -Destination $outputDir -Force -Verbose
403420
}
404421
@@ -448,7 +465,7 @@ extends:
448465
script: |
449466
Write-Verbose -Verbose "Release version: $(PackageVersion)"
450467
451-
$artifacts = Get-ChildItem "$(Pipeline.Workspace)" -Recurse -Include '*.zip', '*.tar.gz', '*.msixbundle'
468+
$artifacts = Get-ChildItem "$(Pipeline.Workspace)" -Recurse -Include '*.zip', '*.tar.gz', '*.msixbundle', '*.rpm', '*.deb'
452469
453470
$artifacts | ForEach-Object {
454471
Write-Verbose -Verbose "Found artifact: $($_.FullName)"
@@ -489,6 +506,8 @@ extends:
489506
$(GitHubReleaseDirectory)\*.zip
490507
$(GitHubReleaseDirectory)\*.tar.gz
491508
$(GitHubReleaseDirectory)\*.msixbundle
509+
$(GitHubReleaseDirectory)\*.rpm
510+
$(GitHubReleaseDirectory)\*.deb
492511
addChangeLog: false
493512
tagSource: 'userSpecifiedTag'
494513
tag: '$(GitHubReleaseVersion)'

build.ps1

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ param(
99
$architecture = 'current',
1010
[switch]$Clippy,
1111
[switch]$SkipBuild,
12-
[ValidateSet('msix','msix-private','msixbundle','tgz','zip')]
12+
[ValidateSet('msix','msix-private','msixbundle','tgz','zip','rpm','deb')]
1313
$packageType,
1414
[switch]$Test,
1515
[switch]$GetPackageVersion,
@@ -857,6 +857,176 @@ if ($packageType -eq 'msixbundle') {
857857
}
858858

859859
Write-Host -ForegroundColor Green "`ntar.gz file is created at $tarFile"
860+
} elseif ($packageType -eq 'rpm') {
861+
if (!$IsLinux) {
862+
throw "RPM package creation is only supported on Linux"
863+
}
864+
865+
# Check if rpmbuild is available
866+
if ($null -eq (Get-Command rpmbuild -ErrorAction Ignore)) {
867+
throw "rpmbuild not found. Please install rpm-build package (e.g., 'sudo apt install rpm build-essential' or 'sudo dnf install rpm-build')"
868+
}
869+
870+
$rpmTarget = Join-Path $PSScriptRoot 'bin' $architecture 'rpm'
871+
if (Test-Path $rpmTarget) {
872+
Remove-Item $rpmTarget -Recurse -ErrorAction Stop -Force
873+
}
874+
875+
New-Item -ItemType Directory $rpmTarget > $null
876+
877+
# Create RPM build directories
878+
$rpmBuildRoot = Join-Path $rpmTarget 'rpmbuild'
879+
$rpmDirs = @('BUILD', 'RPMS', 'SOURCES', 'SPECS', 'SRPMS')
880+
foreach ($dir in $rpmDirs) {
881+
New-Item -ItemType Directory -Path (Join-Path $rpmBuildRoot $dir) -Force > $null
882+
}
883+
884+
# Create a staging directory for the files
885+
$stagingDir = Join-Path $rpmBuildRoot 'SOURCES' 'dsc_files'
886+
New-Item -ItemType Directory $stagingDir > $null
887+
888+
$filesForPackage = $filesForLinuxPackage
889+
890+
foreach ($file in $filesForPackage) {
891+
if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) {
892+
Copy-Item "$target\$file" "$stagingDir\$file" -Recurse -ErrorAction Stop
893+
} else {
894+
Copy-Item "$target\$file" $stagingDir -ErrorAction Stop
895+
}
896+
}
897+
898+
# Determine RPM architecture
899+
$rpmArch = if ($architecture -eq 'current') {
900+
# Detect current system architecture
901+
$currentArch = uname -m
902+
if ($currentArch -eq 'x86_64') {
903+
'x86_64'
904+
} elseif ($currentArch -eq 'aarch64') {
905+
'aarch64'
906+
} else {
907+
throw "Unsupported current architecture for RPM: $currentArch"
908+
}
909+
} elseif ($architecture -eq 'aarch64-unknown-linux-musl' -or $architecture -eq 'aarch64-unknown-linux-gnu') {
910+
'aarch64'
911+
} elseif ($architecture -eq 'x86_64-unknown-linux-musl' -or $architecture -eq 'x86_64-unknown-linux-gnu') {
912+
'x86_64'
913+
} else {
914+
throw "Unsupported architecture for RPM: $architecture"
915+
}
916+
917+
# Read the spec template and replace placeholders
918+
$specTemplate = Get-Content "$PSScriptRoot/packaging/rpm/dsc.spec" -Raw
919+
$specContent = $specTemplate.Replace('VERSION_PLACEHOLDER', $productVersion.Replace('-','~')).Replace('ARCH_PLACEHOLDER', $rpmArch)
920+
$specFile = Join-Path $rpmBuildRoot 'SPECS' 'dsc.spec'
921+
Set-Content -Path $specFile -Value $specContent
922+
923+
Write-Verbose -Verbose "Building RPM package"
924+
$rpmPackageName = "dsc-$productVersion-1.$rpmArch.rpm"
925+
926+
# Build the RPM
927+
rpmbuild -v -bb --define "_topdir $rpmBuildRoot" --buildroot "$rpmBuildRoot/BUILDROOT" $specFile 2>&1 > $rpmTarget/rpmbuild.log
928+
929+
if ($LASTEXITCODE -ne 0) {
930+
Write-Error (Get-Content $rpmTarget/rpmbuild.log -Raw)
931+
throw "Failed to create RPM package"
932+
}
933+
934+
# Copy the RPM to the bin directory
935+
$builtRpm = Get-ChildItem -Path (Join-Path $rpmBuildRoot 'RPMS') -Recurse -Filter '*.rpm' | Select-Object -First 1
936+
if ($null -eq $builtRpm) {
937+
throw "RPM package was not created"
938+
}
939+
940+
$finalRpmPath = Join-Path $PSScriptRoot 'bin' $builtRpm.Name
941+
Copy-Item $builtRpm.FullName $finalRpmPath -Force
942+
943+
Write-Host -ForegroundColor Green "`nRPM package is created at $finalRpmPath"
944+
} elseif ($packageType -eq 'deb') {
945+
if (!$IsLinux) {
946+
throw "DEB package creation is only supported on Linux"
947+
}
948+
949+
# Check if dpkg-deb is available
950+
if ($null -eq (Get-Command dpkg-deb -ErrorAction Ignore)) {
951+
throw "dpkg-deb not found. Please install dpkg package (e.g., 'sudo apt install dpkg' or 'sudo dnf install dpkg')"
952+
}
953+
954+
$debTarget = Join-Path $PSScriptRoot 'bin' $architecture 'deb'
955+
if (Test-Path $debTarget) {
956+
Remove-Item $debTarget -Recurse -ErrorAction Stop -Force
957+
}
958+
959+
New-Item -ItemType Directory $debTarget > $null
960+
961+
# Create DEB package structure
962+
$debBuildRoot = Join-Path $debTarget 'dsc'
963+
$debDirs = @('DEBIAN', 'opt/dsc', 'usr/bin')
964+
foreach ($dir in $debDirs) {
965+
New-Item -ItemType Directory -Path (Join-Path $debBuildRoot $dir) -Force > $null
966+
}
967+
968+
# Copy files to the package directory
969+
$filesForPackage = $filesForLinuxPackage
970+
$stagingDir = Join-Path $debBuildRoot 'opt' 'dsc'
971+
972+
foreach ($file in $filesForPackage) {
973+
if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) {
974+
Copy-Item "$target\$file" "$stagingDir\$file" -Recurse -ErrorAction Stop
975+
} else {
976+
Copy-Item "$target\$file" $stagingDir -ErrorAction Stop
977+
}
978+
}
979+
980+
# Create symlink in usr/bin
981+
$symlinkPath = Join-Path $debBuildRoot 'usr' 'bin' 'dsc'
982+
New-Item -ItemType SymbolicLink -Path $symlinkPath -Target '/opt/dsc/dsc' -Force > $null
983+
984+
# Determine DEB architecture
985+
$debArch = if ($architecture -eq 'current') {
986+
# Detect current system architecture
987+
$currentArch = uname -m
988+
if ($currentArch -eq 'x86_64') {
989+
'amd64'
990+
} elseif ($currentArch -eq 'aarch64') {
991+
'arm64'
992+
} else {
993+
throw "Unsupported current architecture for DEB: $currentArch"
994+
}
995+
} elseif ($architecture -eq 'aarch64-unknown-linux-musl' -or $architecture -eq 'aarch64-unknown-linux-gnu') {
996+
'arm64'
997+
} elseif ($architecture -eq 'x86_64-unknown-linux-musl' -or $architecture -eq 'x86_64-unknown-linux-gnu') {
998+
'amd64'
999+
} else {
1000+
throw "Unsupported architecture for DEB: $architecture"
1001+
}
1002+
1003+
# Read the control template and replace placeholders
1004+
$controlTemplate = Get-Content "$PSScriptRoot/packaging/deb/control" -Raw
1005+
$controlContent = $controlTemplate.Replace('VERSION_PLACEHOLDER', $productVersion).Replace('ARCH_PLACEHOLDER', $debArch)
1006+
$controlFile = Join-Path $debBuildRoot 'DEBIAN' 'control'
1007+
Set-Content -Path $controlFile -Value $controlContent
1008+
1009+
Write-Verbose -Verbose "Building DEB package"
1010+
$debPackageName = "dsc_$productVersion-1_$debArch.deb"
1011+
1012+
# Build the DEB
1013+
dpkg-deb --build $debBuildRoot 2>&1 > $debTarget/debbuild.log
1014+
1015+
if ($LASTEXITCODE -ne 0) {
1016+
Write-Error (Get-Content $debTarget/debbuild.log -Raw)
1017+
throw "Failed to create DEB package"
1018+
}
1019+
1020+
# Move the DEB to the bin directory with the correct name
1021+
$builtDeb = "$debBuildRoot.deb"
1022+
if (!(Test-Path $builtDeb)) {
1023+
throw "DEB package was not created"
1024+
}
1025+
1026+
$finalDebPath = Join-Path $PSScriptRoot 'bin' $debPackageName
1027+
Move-Item $builtDeb $finalDebPath -Force
1028+
1029+
Write-Host -ForegroundColor Green "`nDEB package is created at $finalDebPath"
8601030
}
8611031

8621032
$env:RUST_BACKTRACE=1

packaging/deb/control

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Package: dsc
2+
Version: VERSION_PLACEHOLDER
3+
Section: utils
4+
Priority: optional
5+
Architecture: ARCH_PLACEHOLDER
6+
Maintainer: Microsoft Corporation
7+
Homepage: https://github.com/PowerShell/DSC
8+
Description: DesiredStateConfiguration v3
9+
DSCv3 is the latest iteration of Microsoft's Desired State Configuration
10+
platform. DSCv3 is an open source command line application that abstracts
11+
the management of software components declaratively and idempotently.
12+
DSCv3 runs on Linux, macOS, and Windows without any external dependencies.

packaging/rpm/dsc.spec

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Name: dsc
2+
Version: VERSION_PLACEHOLDER
3+
Release: 1
4+
Summary: DesiredStateConfiguration v3
5+
License: MIT
6+
URL: https://github.com/PowerShell/DSC
7+
BuildArch: ARCH_PLACEHOLDER
8+
9+
%description
10+
DSCv3 is the latest iteration of Microsoft's Desired State Configuration platform.
11+
DSCv3 is an open source command line application that abstracts the management of
12+
software components declaratively and idempotently. DSCv3 runs on Linux, macOS,
13+
and Windows without any external dependencies.
14+
15+
%prep
16+
# No prep needed - files are already built
17+
18+
%build
19+
# No build needed - binary is already compiled
20+
21+
%install
22+
# Create installation directories
23+
mkdir -p $RPM_BUILD_ROOT/opt/dsc
24+
mkdir -p $RPM_BUILD_ROOT/usr/bin
25+
26+
# Copy all files from the source directory
27+
cp -r $RPM_SOURCE_DIR/dsc_files/* $RPM_BUILD_ROOT/opt/dsc/
28+
29+
# Create symlink to make dsc available in PATH
30+
ln -s /opt/dsc/dsc $RPM_BUILD_ROOT/usr/bin/dsc
31+
32+
%files
33+
/opt/dsc/*
34+
/usr/bin/dsc
35+
36+
%changelog
37+
* Wed Oct 22 2025 Microsoft Corporation
38+
- Initial RPM package release

0 commit comments

Comments
 (0)