@@ -7,28 +7,67 @@ import { Utils } from '../../../utils/index.js';
77import { JenvConfig } from './jenv.js' ;
88import { nanoid } from 'nanoid' ;
99
10- export const OPENJDK_SUPPORTED_VERSIONS = [ 8 , 11 , 17 , 21 , 22 ]
1110export const JAVA_VERSION_INTEGER = / ^ \d + $ / ;
1211
12+ // Maps an integer version to the Homebrew Cellar prefix (macOS)
13+ function toBrewPath ( version : number ) : string {
14+ return `/opt/homebrew/Cellar/openjdk@${ version } ` ;
15+ }
16+
17+ // Maps an integer version to the system JVM path (Linux)
18+ // Covers both x86_64 (amd64) and aarch64 (arm64) architectures
19+ async function toLinuxJvmPath ( version : number ) : Promise < string > {
20+ const candidates = [
21+ `/usr/lib/jvm/java-${ version } -openjdk-amd64` ,
22+ `/usr/lib/jvm/java-${ version } -openjdk-arm64` ,
23+ `/usr/lib/jvm/java-${ version } -openjdk` ,
24+ `/usr/lib/jvm/temurin-${ version } ` ,
25+ `/usr/lib/jvm/java-${ version } ` ,
26+ ] ;
27+
28+ for ( const candidate of candidates ) {
29+ if ( await FileUtils . exists ( candidate ) ) {
30+ return candidate ;
31+ }
32+ }
33+
34+ // Return the most common path as the default even if it doesn't exist yet
35+ const isArm = await Utils . isArmArch ( ) ;
36+ return `/usr/lib/jvm/java-${ version } -openjdk-${ isArm ? 'arm64' : 'amd64' } ` ;
37+ }
38+
39+ function parseVersionFromBrewPath ( p : string ) : string | undefined {
40+ return p . split ( '/' ) . at ( 4 ) ?. split ( '@' ) . at ( 1 ) ;
41+ }
42+
1343export class JenvAddParameter extends ArrayStatefulParameter < JenvConfig , string > {
1444 getSettings ( ) : ArrayParameterSetting {
1545 return {
1646 type : 'array' ,
1747 itemType : 'directory' ,
1848 isElementEqual : ( a , b ) => b . includes ( a ) ,
1949 transformation : {
20- to : ( input : string [ ] ) =>
21- input . map ( ( i ) => {
22- if ( OPENJDK_SUPPORTED_VERSIONS . includes ( Number . parseInt ( i , 10 ) ) ) {
23- return `/opt/homebrew/Cellar/openjdk@${ Number . parseInt ( i , 10 ) } `
50+ to : async ( input : string [ ] ) =>
51+ Promise . all ( input . map ( async ( i ) => {
52+ const parsed = Number . parseInt ( i , 10 ) ;
53+ if ( JAVA_VERSION_INTEGER . test ( i ) && ! Number . isNaN ( parsed ) ) {
54+ return Utils . isMacOS ( )
55+ ? toBrewPath ( parsed )
56+ : await toLinuxJvmPath ( parsed ) ;
2457 }
2558
2659 return i ;
27- } ) ,
60+ } ) ) ,
2861 // De-dupe the results for imports.
2962 from : ( output : string [ ] ) => [ ...new Set ( output . map ( ( i ) => {
3063 if ( i . startsWith ( '/opt/homebrew/Cellar/openjdk@' ) ) {
31- return i . split ( '/' ) . at ( 4 ) ?. split ( '@' ) . at ( 1 )
64+ return parseVersionFromBrewPath ( i ) ;
65+ }
66+
67+ // Linux: /usr/lib/jvm/java-17-openjdk-amd64 → "17"
68+ const linuxMatch = i . match ( / \/ u s r \/ l i b \/ j v m \/ (?: j a v a - | t e m u r i n - ) ( \d + ) / ) ;
69+ if ( linuxMatch ) {
70+ return linuxMatch [ 1 ] ;
3271 }
3372
3473 return i ;
@@ -41,12 +80,12 @@ export class JenvAddParameter extends ArrayStatefulParameter<JenvConfig, string>
4180 const $ = getPty ( ) ;
4281
4382 const { data : jenvRoot } = await $ . spawn ( 'jenv root' )
44- const versions = ( await fs . readdir ( `${ jenvRoot } /versions` ) ) . filter ( ( v ) => v !== '.DS_store' ) ;
83+ const versions = ( await fs . readdir ( `${ jenvRoot . trim ( ) } /versions` ) ) . filter ( ( v ) => v !== '.DS_store' && v !== '.DS_Store ') ;
4584
4685 // We use a set because jenv sets an alias for 11.0.24, 11.0 and 11. We only care about the original location here
4786 const versionPaths = new Set (
4887 await Promise . all ( versions . map ( ( v ) =>
49- fs . readlink ( `${ jenvRoot } /versions/${ v } ` )
88+ fs . readlink ( `${ jenvRoot . trim ( ) } /versions/${ v } ` )
5089 ) )
5190 )
5291
@@ -58,42 +97,47 @@ export class JenvAddParameter extends ArrayStatefulParameter<JenvConfig, string>
5897 // Re-map the path back to what was provided in the config
5998 . map ( ( v ) => {
6099 const matched = params ?. find ( ( p ) => v . includes ( p ) ) ;
61- return matched === undefined
62- ? v
63- : matched ;
100+ return matched === undefined ? v : matched ;
64101 } )
65102 . filter ( ( v ) => {
66- const versionStr = v . split ( '/' ) . at ( 4 ) ! . split ( '@' ) . at ( 1 ) ! ;
67- return installedVersions . includes ( versionStr ) ;
103+ // macOS Homebrew path: /opt/homebrew/Cellar/openjdk@17/...
104+ if ( v . startsWith ( '/opt/homebrew/Cellar/openjdk@' ) ) {
105+ const versionStr = parseVersionFromBrewPath ( v ) ;
106+ return versionStr !== undefined && installedVersions . some ( ( iv ) => iv . startsWith ( versionStr ) ) ;
107+ }
108+
109+ // Linux JVM path: /usr/lib/jvm/java-17-openjdk-amd64
110+ const linuxMatch = v . match ( / \/ u s r \/ l i b \/ j v m \/ (?: j a v a - | t e m u r i n - ) ( \d + ) / ) ;
111+ if ( linuxMatch ) {
112+ const versionStr = linuxMatch [ 1 ] ;
113+ return installedVersions . some ( ( iv ) => iv . startsWith ( versionStr ) ) ;
114+ }
115+
116+ // Generic path: match against installed version strings
117+ return installedVersions . some ( ( iv ) => v . includes ( iv ) ) ;
68118 } ) ;
69119 }
70120
71121 override async addItem ( param : string ) : Promise < void > {
72122 let location = param ;
73123
74- // Check if we should auto install it from homebrew first
124+ // macOS: auto- install from Homebrew
75125 if ( param . startsWith ( '/opt/homebrew/Cellar/openjdk@' ) ) {
76-
77- // Doesn't currently exist on the file system, let's parse and install from homebrew before adding
78126 if ( ! ( await FileUtils . exists ( param ) ) ) {
79127 const isHomebrewInstalled = await Utils . isHomebrewInstalled ( ) ;
80128 if ( ! isHomebrewInstalled ) {
81129 throw new Error ( 'Homebrew not detected. Cannot automatically install java version. Jenv does not automatically install' +
82130 ' java versions, see the jenv docs: https://www.jenv.be. Please manually install a version of java and provide a path to the jenv resource' )
83131 }
84132
85- const versionStr = param . split ( '/' ) . at ( 4 ) ?. split ( '@' ) . at ( 1 ) ;
133+ const versionStr = parseVersionFromBrewPath ( param ) ;
86134 if ( ! versionStr ) {
87135 throw new Error ( `jenv: malformed version str: ${ versionStr } ` )
88136 }
89137
90138 const parsedVersion = Number . parseInt ( versionStr , 10 )
91- if ( ! OPENJDK_SUPPORTED_VERSIONS . includes ( parsedVersion ) ) {
92- throw new Error ( `Unsupported version of java specified. Only [${ OPENJDK_SUPPORTED_VERSIONS . join ( ', ' ) } ] is supported` )
93- }
94-
95139 const $ = getPty ( ) ;
96- const openjdkName = ( parsedVersion === 22 ) ? 'openjdk' : `openjdk@${ parsedVersion } ` ;
140+ const openjdkName = `openjdk@${ parsedVersion } ` ;
97141 const { status } = await $ . spawnSafe ( `brew list --formula -1 ${ openjdkName } ` , { interactive : true } ) ;
98142
99143 // That version is not currently installed with homebrew. Let's install it
@@ -107,7 +151,7 @@ export class JenvAddParameter extends ArrayStatefulParameter<JenvConfig, string>
107151 throw new Error ( 'Unable to determine location of jdk installed by homebrew. Please report this to the Codify team' ) ;
108152 }
109153
110- // Already exists on the file system let's re-map to the actual path
154+ // Already exists on the file system: re-map to the actual versioned path
111155 } else if ( ! param . endsWith ( 'libexec/openjdk.jdk/Contents/Home' ) ) {
112156 const versions = ( await fs . readdir ( param ) ) . filter ( ( v ) => v !== '.DS_Store' )
113157 const sortedVersions = semver . sort ( versions ) ;
@@ -117,6 +161,24 @@ export class JenvAddParameter extends ArrayStatefulParameter<JenvConfig, string>
117161 }
118162 }
119163
164+ // Linux: auto-install via apt
165+ if ( Utils . isLinux ( ) ) {
166+ const linuxMatch = param . match ( / \/ u s r \/ l i b \/ j v m \/ (?: j a v a - | t e m u r i n - ) ( \d + ) / ) ;
167+ if ( linuxMatch && ! ( await FileUtils . exists ( param ) ) ) {
168+ const version = linuxMatch [ 1 ] ;
169+ const $ = getPty ( ) ;
170+ const packageName = `openjdk-${ version } -jdk` ;
171+ const { status } = await $ . spawnSafe ( `dpkg -s ${ packageName } ` , { interactive : true , requiresRoot : true } ) ;
172+
173+ if ( status === SpawnStatus . ERROR ) {
174+ console . log ( `apt detected. Attempting to install java version ${ packageName } automatically` )
175+ await $ . spawn ( `apt-get install -y ${ packageName } ` , { interactive : true , requiresRoot : true } )
176+ }
177+
178+ location = await toLinuxJvmPath ( Number . parseInt ( version , 10 ) ) ;
179+ }
180+ }
181+
120182 const $ = getPty ( ) ;
121183 try {
122184 await $ . spawn ( `jenv add ${ location } ` , { interactive : true } ) ;
@@ -135,7 +197,7 @@ export class JenvAddParameter extends ArrayStatefulParameter<JenvConfig, string>
135197 const isHomebrewInstalled = await Utils . isHomebrewInstalled ( ) ;
136198
137199 if ( isHomebrewInstalled && param . startsWith ( '/opt/homebrew/Cellar/openjdk@' ) ) {
138- const versionStr = param . split ( '/' ) . at ( 4 ) ?. split ( '@' ) . at ( 1 ) ;
200+ const versionStr = parseVersionFromBrewPath ( param ) ;
139201 if ( ! versionStr ) {
140202 throw new Error ( `jenv: malformed version str: ${ versionStr } ` )
141203 }
@@ -152,6 +214,16 @@ export class JenvAddParameter extends ArrayStatefulParameter<JenvConfig, string>
152214 return
153215 }
154216
217+ if ( Utils . isLinux ( ) ) {
218+ const linuxMatch = param . match ( / \/ u s r \/ l i b \/ j v m \/ (?: j a v a - | t e m u r i n - ) ( \d + ) / ) ;
219+ if ( linuxMatch ) {
220+ const version = linuxMatch [ 1 ] ;
221+ await $ . spawn ( `jenv remove ${ param } ` , { interactive : true } )
222+ await $ . spawn ( `sudo apt-get remove -y openjdk-${ version } -jdk` , { interactive : true } )
223+ return ;
224+ }
225+ }
226+
155227 await $ . spawn ( `jenv remove ${ param } ` , { interactive : true } ) ;
156228 }
157229
0 commit comments