@@ -35,36 +35,41 @@ export async function setupAptPack(packages: AptPackage[], update = false): Prom
3535
3636 process . env . DEBIAN_FRONTEND = "noninteractive"
3737
38- if ( ! didUpdate || update ) {
38+ // Update the repos if needed
39+ if ( update ) {
3940 updateRepos ( apt )
4041 didUpdate = true
4142 }
4243
43- if ( ! didInit ) {
44- await initApt ( apt )
45- didInit = true
46- }
44+ // Add the repos if needed
45+ await addRepositories ( apt , packages )
4746
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 ) ) )
4949
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 ) ) ) )
5552
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
5762 }
5863
59- const aptArgs = await Promise . all ( packages . map ( ( pack ) => getAptArg ( pack . name , pack . version ) ) )
64+ // Install
6065 try {
61- execRootSync ( apt , [ "install" , "--fix-broken" , "-y" , ...aptArgs ] )
66+ execRootSync ( apt , [ "install" , "--fix-broken" , "-y" , ...qualifiedPacks ] )
6267 } catch ( err ) {
6368 if ( "stderr" in ( err as ExecaError ) ) {
6469 const stderr = ( err as ExecaError ) . stderr
6570 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 ] )
6873 }
6974 } else {
7075 throw err
@@ -81,6 +86,23 @@ export enum AptPackageType {
8186 None = 3 ,
8287}
8388
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+
84106export async function aptPackageType ( name : string , version : string | undefined ) : Promise < AptPackageType > {
85107 if ( version !== undefined && version !== "" ) {
86108 const { stdout } = await execa ( "apt-cache" , [
@@ -113,6 +135,13 @@ export async function aptPackageType(name: string, version: string | undefined):
113135 // ignore
114136 }
115137
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+
116145 return AptPackageType . None
117146}
118147
@@ -148,17 +177,27 @@ function updateRepos(apt: string) {
148177 execRootSync ( apt , apt !== "nala" ? [ "update" , "-y" ] : [ "update" ] )
149178}
150179
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) */
152188async 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+
162201 const promises : Promise < string | void > [ ] = [
163202 addAptKeyViaServer ( [ "3B4FE6ACC0B21F32" , "40976EAF437D05B5" ] , "setup-cpp-ubuntu-archive.gpg" ) ,
164203 addAptKeyViaServer ( [ "1E9377A2BA9EF27F" ] , "launchpad-toolchain.gpg" ) ,
@@ -229,7 +268,22 @@ export async function updateAptAlternatives(name: string, path: string, priority
229268 }
230269}
231270
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 ) {
233287 try {
234288 // check if a package matching the regexp is installed
235289 const { stdout } = await execa ( "dpkg" , [ "-l" , regexp ] )
0 commit comments