@@ -35,36 +35,41 @@ export async function setupAptPack(packages: AptPackage[], update = false): Prom
35
35
36
36
process . env . DEBIAN_FRONTEND = "noninteractive"
37
37
38
- if ( ! didUpdate || update ) {
38
+ // Update the repos if needed
39
+ if ( update ) {
39
40
updateRepos ( apt )
40
41
didUpdate = true
41
42
}
42
43
43
- if ( ! didInit ) {
44
- await initApt ( apt )
45
- didInit = true
46
- }
44
+ // Add the repos if needed
45
+ await addRepositories ( apt , packages )
47
46
48
- const allRepositories = [ ...new Set ( packages . flatMap ( ( pack ) => pack . repositories ?? [ ] ) ) ]
47
+ // Qualify the packages into full package name/version
48
+ let qualifiedPacks = await Promise . all ( packages . map ( ( pack ) => getAptArg ( pack . name , pack . version ) ) )
49
49
50
- if ( allRepositories . length !== 0 ) {
51
- for ( const repo of allRepositories ) {
52
- // eslint-disable-next-line no-await-in-loop
53
- execRootSync ( "add-apt-repository" , [ "-y" , repo ] )
54
- }
50
+ // find the packages that are not installed
51
+ qualifiedPacks = await Promise . all ( qualifiedPacks . filter ( async ( pack ) => ! ( await isPackageInstalled ( pack ) ) ) )
55
52
56
- updateRepos ( apt )
53
+ if ( qualifiedPacks . length === 0 ) {
54
+ info ( "All packages are already installed" )
55
+ return { binDir : "/usr/bin/" }
56
+ }
57
+
58
+ // Initialize apt if needed
59
+ if ( ! didInit ) {
60
+ await initApt ( apt )
61
+ didInit = true
57
62
}
58
63
59
- const aptArgs = await Promise . all ( packages . map ( ( pack ) => getAptArg ( pack . name , pack . version ) ) )
64
+ // Install
60
65
try {
61
- execRootSync ( apt , [ "install" , "--fix-broken" , "-y" , ...aptArgs ] )
66
+ execRootSync ( apt , [ "install" , "--fix-broken" , "-y" , ...qualifiedPacks ] )
62
67
} catch ( err ) {
63
68
if ( "stderr" in ( err as ExecaError ) ) {
64
69
const stderr = ( err as ExecaError ) . stderr
65
70
if ( retryErrors . some ( ( error ) => stderr . includes ( error ) ) ) {
66
- warning ( `Failed to install packages ${ aptArgs } . Retrying...` )
67
- execRootSync ( apt , [ "install" , "--fix-broken" , "-y" , ...aptArgs ] )
71
+ warning ( `Failed to install packages ${ qualifiedPacks } . Retrying...` )
72
+ execRootSync ( apt , [ "install" , "--fix-broken" , "-y" , ...qualifiedPacks ] )
68
73
}
69
74
} else {
70
75
throw err
@@ -81,6 +86,23 @@ export enum AptPackageType {
81
86
None = 3 ,
82
87
}
83
88
89
+ async function addRepositories ( apt : string , packages : AptPackage [ ] ) {
90
+ const allRepositories = [ ...new Set ( packages . flatMap ( ( pack ) => pack . repositories ?? [ ] ) ) ]
91
+ if ( allRepositories . length !== 0 ) {
92
+ if ( ! didInit ) {
93
+ await initApt ( apt )
94
+ didInit = true
95
+ }
96
+ await installAddAptRepo ( )
97
+ for ( const repo of allRepositories ) {
98
+ // eslint-disable-next-line no-await-in-loop
99
+ execRootSync ( "add-apt-repository" , [ "-y" , repo ] )
100
+ }
101
+ updateRepos ( apt )
102
+ didUpdate = true
103
+ }
104
+ }
105
+
84
106
export async function aptPackageType ( name : string , version : string | undefined ) : Promise < AptPackageType > {
85
107
if ( version !== undefined && version !== "" ) {
86
108
const { stdout } = await execa ( "apt-cache" , [
@@ -113,6 +135,13 @@ export async function aptPackageType(name: string, version: string | undefined):
113
135
// ignore
114
136
}
115
137
138
+ // If apt-cache fails, update the repos and try again
139
+ if ( ! didUpdate ) {
140
+ updateRepos ( getApt ( ) )
141
+ didUpdate = true
142
+ return aptPackageType ( name , version )
143
+ }
144
+
116
145
return AptPackageType . None
117
146
}
118
147
@@ -148,17 +177,27 @@ function updateRepos(apt: string) {
148
177
execRootSync ( apt , apt !== "nala" ? [ "update" , "-y" ] : [ "update" ] )
149
178
}
150
179
151
- /** Install apt utils and certificates (usually missing from docker containers) */
180
+ async function installAddAptRepo ( ) {
181
+ if ( await isPackageInstalled ( "software-properties-common" ) ) {
182
+ return
183
+ }
184
+ execRootSync ( "apt-get" , [ "install" , "-y" , "--fix-broken" , "software-properties-common" ] )
185
+ }
186
+
187
+ /** Install gnupg and certificates (usually missing from docker containers) */
152
188
async function initApt ( apt : string ) {
153
- execRootSync ( apt , [
154
- "install" ,
155
- "--fix-broken" ,
156
- "-y" ,
157
- "software-properties-common" ,
158
- "apt-utils" ,
159
- "ca-certificates" ,
160
- "gnupg" ,
161
- ] )
189
+ // Update the repos if needed
190
+ if ( ! didUpdate ) {
191
+ updateRepos ( apt )
192
+ didUpdate = true
193
+ }
194
+
195
+ const toInstall = [ "ca-certificates" , "gnupg" , "apt-utils" ] . filter ( async ( pack ) => ! ( await isPackageInstalled ( pack ) ) )
196
+
197
+ if ( toInstall . length !== 0 ) {
198
+ execRootSync ( apt , [ "install" , "-y" , "--fix-broken" , ...toInstall ] )
199
+ }
200
+
162
201
const promises : Promise < string | void > [ ] = [
163
202
addAptKeyViaServer ( [ "3B4FE6ACC0B21F32" , "40976EAF437D05B5" ] , "setup-cpp-ubuntu-archive.gpg" ) ,
164
203
addAptKeyViaServer ( [ "1E9377A2BA9EF27F" ] , "launchpad-toolchain.gpg" ) ,
@@ -229,7 +268,22 @@ export async function updateAptAlternatives(name: string, path: string, priority
229
268
}
230
269
}
231
270
232
- export async function isPackageInstalled ( regexp : string ) {
271
+ export async function isPackageInstalled ( pack : string ) {
272
+ try {
273
+ // check if a package is installed
274
+ const { stdout } = await execa ( "dpkg" , [ "-s" , pack ] )
275
+ if ( typeof stdout !== "string" ) {
276
+ return false
277
+ }
278
+ const lines = stdout . split ( "\n" )
279
+ // check if the output contains a line that starts with "Status: install ok installed"
280
+ return lines . some ( ( line ) => line . startsWith ( "Status: install ok installed" ) )
281
+ } catch {
282
+ return false
283
+ }
284
+ }
285
+
286
+ export async function isPackageRegexInstalled ( regexp : string ) {
233
287
try {
234
288
// check if a package matching the regexp is installed
235
289
const { stdout } = await execa ( "dpkg" , [ "-l" , regexp ] )
0 commit comments