@@ -5,6 +5,7 @@ import * as path from "path";
55import * as ChildProcess from "child_process" ;
66import { config } from './extension' ;
77import { determineOutputFolder , downloadFileInteractive } from './installer' ;
8+ import { reqText } from './util' ;
89
910export interface DetectedCompiler {
1011 /**
@@ -31,6 +32,9 @@ type UIQuickPickItem = vscode.QuickPickItem & { kind?: number, installInfo?: any
3132export async function setupCompilersUI ( ) {
3233 const introQuickPick = vscode . window . createQuickPick ( ) ;
3334 introQuickPick . title = "Setup auto-detected compiler or manually configure compiler" ;
35+ introQuickPick . busy = true ;
36+ introQuickPick . items = [ { label : "Detecting compilers..." } ] ;
37+ introQuickPick . show ( ) ;
3438 const compilers : DetectedCompiler [ ] = await listCompilers ( ) ;
3539 let items : UIQuickPickItem [ ] = [ ] ;
3640 for ( let i = 0 ; i < compilers . length ; i ++ ) {
@@ -52,6 +56,8 @@ export async function setupCompilersUI() {
5256 }
5357 if ( compiler . frontendVersion && compiler . frontendVersion != compiler . version )
5458 versionStrings . push ( "spec version " + compiler . frontendVersion ) ;
59+ if ( ! compiler . inPath && compiler . path )
60+ versionStrings . push ( compiler . path ) ;
5561 items . push ( {
5662 label : compiler . has ,
5763 description : versionStrings . length > 0 ? versionStrings . join ( " ・ " ) : undefined ,
@@ -82,13 +88,13 @@ export async function setupCompilersUI() {
8288 description : "GCC-based D compiler ・ stable, great optimization"
8389 } ) ;
8490 items . push ( manualSelect = {
85- label : "Select installation folder " ,
91+ label : "Select installed executable " ,
8692 description : "if you have already installed a D compiler that is not being picked up"
8793 } ) ;
8894 introQuickPick . items = items ;
89- introQuickPick . show ( ) ;
95+ introQuickPick . busy = false ;
9096
91- introQuickPick . onDidAccept ( ( e ) => {
97+ introQuickPick . onDidAccept ( async ( e ) => {
9298 let selection = < UIQuickPickItem > introQuickPick . selectedItems [ 0 ] ;
9399 if ( selection . kind === 2 )
94100 return ;
@@ -97,23 +103,33 @@ export async function setupCompilersUI() {
97103 if ( selection . installInfo ) {
98104 showDetectedCompilerInstallPrompt ( selection . installInfo ) ;
99105 } else {
106+ function isGlobalInstallSh ( ) {
107+ let dir = getDefaultInstallShDir ( ) ;
108+ return dir && fs . existsSync ( dir ) ;
109+ }
110+ let latest ;
100111 switch ( selection ) {
101112 case dmdItem :
113+ latest = process . platform == "win32" && await readHTTP ( "http://downloads.dlang.org/releases/LATEST" ) ;
102114 showCompilerInstallationPrompt ( "DMD" , [
103115 { label : "See releases" , website : "https://dlang.org/download.html#dmd" } ,
104- { platform : "win32" , label : "Run installer" , downloadAndRun : "https://s3.us-west-2.amazonaws.com/downloads.dlang.org/releases/2021/dmd-2.098.0.exe" } ,
116+ latest && { platform : "win32" , label : "Run installer" , downloadAndRun : "http://downloads.dlang.org/releases/2.x/" + latest + "/dmd-" + latest + ".exe" } ,
117+ { label : "Portable install (in existing ~/dlang)" , installSh : "install dmd,dub" , binTest : "bash" , global : true , platform : isGlobalInstallSh } ,
105118 { label : "Portable install" , installSh : "install dmd,dub" , binTest : "bash" } ,
106119 { platform : "linux" , label : "System install" , command : "pacman -S dlang-dmd" , binTest : "pacman" } ,
107120 { platform : "linux" , label : "System install" , command : "layman -a dlang" , binTest : "layman" } ,
108121 { platform : "darwin" , label : "System install" , command : "brew install dmd" , binTest : "brew" } ,
109122 { platform : "linux" , label : "System install" , command : "nix-env -iA nixpkgs.dmd" , binTest : "nix-env" } ,
110123 { platform : "linux" , label : "System install" , command : "zypper install dmd" , binTest : "zypper" } ,
124+ { platform : "linux" , label : "System install" , command : "xbps-install -S dmd" , binTest : "xbps-install" } ,
111125 ] ) ;
112126 break ;
113127 case ldcItem :
128+ latest = process . platform == "win32" && await readHTTP ( "http://ldc-developers.github.io/LATEST" ) ;
114129 showCompilerInstallationPrompt ( "LDC" , [
115130 { label : "See releases" , website : "https://github.com/ldc-developers/ldc/releases" } ,
116- { platform : "win32" , label : "Run installer" , downloadAndRun : "https://github.com/ldc-developers/ldc/releases/download/v1.28.0/ldc2-1.28.0-windows-multilib.exe" } ,
131+ latest && { platform : "win32" , label : "Run installer" , downloadAndRun : "https://github.com/ldc-developers/ldc/releases/download/v" + latest + "/ldc2-" + latest + "-windows-multilib.exe" } ,
132+ { label : "Portable install (in existing ~/dlang)" , installSh : "install ldc,dub" , binTest : "bash" , global : true , platform : isGlobalInstallSh } ,
117133 { label : "Portable install" , installSh : "install ldc,dub" , binTest : "bash" } ,
118134 { label : "System install" , command : "brew install ldc" , binTest : "brew" } ,
119135 { platform : "linux" , label : "System install" , command : "apk add ldc" , binTest : "apk" } ,
@@ -125,18 +141,22 @@ export async function setupCompilersUI() {
125141 { platform : "linux" , label : "System install" , command : "layman -a ldc" , binTest : "layman" } ,
126142 { platform : "darwin" , label : "System install" , command : "brew install ldc" , binTest : "brew" } ,
127143 { platform : "linux" , label : "System install" , command : "nix-env -i ldc" , binTest : "nix-env" } ,
144+ { platform : "linux" , label : "System install" , command : "xbps-install -S ldc" , binTest : "xbps-install" } ,
128145 ] ) ;
129146 break ;
130147 case gdcItem :
131148 showCompilerInstallationPrompt ( "GDC" , [
132149 { label : "View Project website" , website : "https://gdcproject.org/downloads" } ,
133150 { platform : "win32" , label : "Install through WinLibs" , website : "https://winlibs.com" } ,
134- { platform : "linux" , label : "Portable install" , installSh : "install gdc,dub" } ,
151+ // no install.sh for GDC because the version is ancient! (installing gcc 4.8.5, FE 2.068.2)
152+ // { platform: () => isGlobalInstallSh() && process.platform == "linux", label: "Portable install (in existing ~/dlang)", installSh: "install gdc,dub", global: true },
153+ // { platform: "linux", label: "Portable install", installSh: "install gdc,dub" },
135154 { platform : "linux" , label : "System install" , command : "pacman -S gcc-d" , binTest : "pacman" } ,
136155 { platform : "linux" , label : "System install" , command : "apt install gdc" , binTest : "apt" } ,
137156 ] ) ;
138157 break ;
139158 case manualSelect :
159+ doManualSelect ( ) ;
140160 break ;
141161 default :
142162 console . error ( "invalid selection" ) ;
@@ -147,20 +167,75 @@ export async function setupCompilersUI() {
147167 } ) ;
148168}
149169
170+ async function readHTTP ( uri : string ) : Promise < string | undefined > {
171+ try {
172+ return ( await reqText ( undefined , 3000 ) . get ( uri ) ) . data ;
173+ } catch ( e ) {
174+ console . log ( "could not fetch" , uri , e ) ;
175+ return undefined ;
176+ }
177+ }
178+
179+ async function doManualSelect ( ) : Promise < void > {
180+ let files = await vscode . window . showOpenDialog ( {
181+ title : "Select compiler executable"
182+ } ) ;
183+ if ( files && files . length > 0 ) {
184+ if ( files . length > 1 ) {
185+ vscode . window . showWarningMessage ( "ignoring more than 1 file" ) ;
186+ }
187+ let selectedPath = files [ 0 ] . fsPath ;
188+ let filename = path . basename ( selectedPath ) ;
189+ let type = getCompilerTypeFromPrefix ( filename ) ;
190+ if ( ! type ) {
191+ let tryAgain = "Try Again" ;
192+ vscode . window . showErrorMessage ( "Could not detect compiler type from executable name (tested for DMD, LDC and GDC) - make sure you open the compiler executable and name it correctly!" , tryAgain )
193+ . then ( b => {
194+ if ( b == tryAgain )
195+ doManualSelect ( ) ;
196+ } ) ;
197+ } else {
198+ let result = await checkCompiler ( type , selectedPath ) ;
199+ if ( ! result . has ) {
200+ let tryAgain = "Try Again" ;
201+ vscode . window . showErrorMessage ( "The selected file was not executable or did not work with. Is the selected file a DMD, LDC or GDB executable?" , tryAgain )
202+ . then ( b => {
203+ if ( b == tryAgain )
204+ doManualSelect ( ) ;
205+ } ) ;
206+ return ;
207+ }
208+
209+ if ( ! result . version && ! result . frontendVersion ) {
210+ let tryAgain = "Try Again" ;
211+ let ignore = "Ignore" ;
212+ let choice = await vscode . window . showWarningMessage ( "Could not detect the compiler version from the executable. Is the selected file a DMD, LDC or GDB executable?" , tryAgain ) ;
213+ if ( choice == tryAgain )
214+ return doManualSelect ( ) ;
215+ else if ( choice != ignore )
216+ return ;
217+ }
218+
219+ await showDetectedCompilerInstallPrompt ( result ) ;
220+ }
221+ }
222+ }
223+
150224type LabelWebsiteButton = { label : string , platform ?: NodeJS . Platform | Function , binTest ?: string , website : string } ;
151225type LabelDownloadButton = { label : string , platform ?: NodeJS . Platform | Function , binTest ?: string , downloadAndRun : string } ;
152226type LabelCommandButton = { label : string , platform ?: NodeJS . Platform | Function , binTest ?: string , command : string } ;
153- type LabelInstallShButton = { label : string , platform ?: NodeJS . Platform | Function , binTest ?: string , installSh : string } ;
227+ type LabelInstallShButton = { label : string , platform ?: NodeJS . Platform | Function , binTest ?: string , installSh : string , global ?: boolean } ;
154228
155229type InstallButtonType = LabelWebsiteButton | LabelDownloadButton | LabelCommandButton | LabelInstallShButton ;
156230type InstallQuickPickItem = vscode . QuickPickItem & { button : InstallButtonType } ;
157231
158- async function showCompilerInstallationPrompt ( name : string , buttons : InstallButtonType [ ] ) {
232+ async function showCompilerInstallationPrompt ( name : string , buttons : ( InstallButtonType | false | null | undefined | "" ) [ ] ) {
159233 const installPrompt = vscode . window . createQuickPick ( ) ;
160234 installPrompt . title = "Install " + name + " compiler" ;
161235 let items : InstallQuickPickItem [ ] = [ ] ;
162236 for ( let i = 0 ; i < buttons . length ; i ++ ) {
163237 const button = buttons [ i ] ;
238+ if ( ! button ) continue ;
164239 if ( button . platform ) {
165240 if ( typeof button . platform == "function" ) {
166241 if ( ! button . platform ( ) )
@@ -260,7 +335,8 @@ async function showCompilerInstallationPrompt(name: string, buttons: InstallButt
260335 runTerminal ( ( < LabelCommandButton > selection ) . command ) ;
261336 } else if ( ( < LabelInstallShButton > selection ) . installSh ) {
262337 let installSh = codedContext . asAbsolutePath ( "res/exe/install.sh" ) . replace ( / \\ / g, '\\\\' ) ;
263- runTerminal ( ( await testBinExists ( "bash" ) ) + " \"" + installSh + "\" " + ( < LabelInstallShButton > selection ) . installSh ) ;
338+ let installDir = getLocalCompilersDir ( ) . replace ( / \\ / g, '\\\\' ) ;
339+ runTerminal ( `${ await testBinExists ( "bash" ) } \"${ installSh } \" -p ${ installDir } ${ ( < LabelInstallShButton > selection ) . installSh } ` ) ;
264340 }
265341 }
266342 } ) ;
@@ -360,10 +436,16 @@ export async function checkCompilers(): Promise<DetectedCompiler> {
360436 for ( let i = 0 ; i < compilers . length ; i ++ ) {
361437 const compiler = compilers [ i ] ;
362438 if ( compiler . has ) {
439+ function isBetterVer ( vs : number ) {
440+ if ( vs == - 1 ) return true ;
441+ var a = compilers [ i ] . frontendVersion || compilers [ i ] . version || "0" ;
442+ var b = compilers [ vs ] . frontendVersion || compilers [ vs ] . version || "0" ;
443+ return cmpVerGeneric ( a , b ) > 0 ;
444+ }
363445 switch ( compiler . has ) {
364- case "dmd" : dmdIndex = i ; break ;
365- case "ldc" : ldcIndex = i ; break ;
366- case "gdc" : gdcIndex = i ; break ;
446+ case "dmd" : if ( isBetterVer ( dmdIndex ) ) dmdIndex = i ; break ;
447+ case "ldc" : if ( isBetterVer ( ldcIndex ) ) ldcIndex = i ; break ;
448+ case "gdc" : if ( isBetterVer ( gdcIndex ) ) gdcIndex = i ; break ;
367449 default : console . error ( "unexpected state in code-d?!" ) ; break ;
368450 }
369451 }
@@ -379,6 +461,26 @@ export async function checkCompilers(): Promise<DetectedCompiler> {
379461 return { has : false , path : fallbackPath } ;
380462}
381463
464+ function cmpVerGeneric ( a : string , b : string ) : number {
465+ var as = a . split ( / [ \s \. \- ] + / g) . map ( i => parseInt ( i ) ) . filter ( n => isFinite ( n ) ) ;
466+ var bs = b . split ( / [ \s \. \- ] + / g) . map ( i => parseInt ( i ) ) . filter ( n => isFinite ( n ) ) ;
467+ return as < bs ? - 1 : as > bs ? 1 : 0 ;
468+ }
469+
470+ function getDefaultInstallShDir ( ) : string | undefined {
471+ if ( process . platform == "win32" ) {
472+ return process . env . USERPROFILE ;
473+ } else if ( process . env . HOME ) {
474+ return path . join ( process . env . HOME , "dlang" ) ;
475+ } else {
476+ return undefined ;
477+ }
478+ }
479+
480+ function getLocalCompilersDir ( ) : string {
481+ return path . join ( determineOutputFolder ( ) , "compilers" ) ;
482+ }
483+
382484let listCompilersCache : DetectedCompiler [ ] | undefined = undefined ;
383485export async function listCompilers ( ) : Promise < DetectedCompiler [ ] > {
384486 if ( listCompilersCache !== undefined )
@@ -391,6 +493,8 @@ export async function listCompilersImpl(): Promise<DetectedCompiler[]> {
391493 const compilers = [ "dmd" , "ldc2" , "ldc" , "gdc" , "gcc" ] ;
392494 let ret : DetectedCompiler [ ] = [ ] ;
393495 let fallbackPath : string | undefined = undefined ;
496+
497+ // test compilers in $PATH
394498 for ( let i = 0 ; i < compilers . length ; i ++ ) {
395499 const check = compilers [ i ] ;
396500 let result = await checkCompiler ( < any > check ) ;
@@ -402,11 +506,109 @@ export async function listCompilersImpl(): Promise<DetectedCompiler[]> {
402506 i ++ ; // skip ldc / gcc
403507 }
404508 }
509+
510+ async function testInstallShPath ( dir : string , type : "dmd" | "ldc" | "gdc" ) {
511+ let activateFile = process . platform == "win32" ? "activate.bat" : "activate" ;
512+ let activateContent : string | undefined = await new Promise ( ( resolve ) => {
513+ fs . readFile ( path . join ( dir , activateFile ) , { encoding : "utf8" } , ( err , data ) => {
514+ if ( err )
515+ return resolve ( undefined ) ;
516+ resolve ( data )
517+ } ) ;
518+ } ) ;
519+
520+ if ( ! activateContent )
521+ return ;
522+
523+ let foundPaths : string [ ] = [ ] ;
524+ activatePathEnvironmentRegex . lastIndex = 0 ;
525+ let m : RegExpMatchArray | null | undefined ;
526+ while ( m = activatePathEnvironmentRegex . exec ( activateContent ) ) {
527+ // unshift because the scripts are prepending and we want 0 to be most specific
528+ // at least on windows this will prefer the bin64 over bin folder
529+ foundPaths . unshift . apply ( foundPaths , m [ 1 ] . split ( process . platform == "win32" ? / ; / g : / : / g) ) ;
530+ }
531+
532+ for ( var i = 0 ; i < foundPaths . length ; i ++ ) {
533+ let exeName : string = type ;
534+ if ( type == "ldc" )
535+ exeName += "2" ; // ldc2.exe
536+ if ( process . platform == "win32" )
537+ exeName += ".exe" ;
538+ let exePath = path . join ( foundPaths [ i ] , exeName ) ;
539+
540+ if ( ! fs . existsSync ( exePath ) )
541+ continue ;
542+
543+ let result = await checkCompiler ( type , exePath ) ;
544+ fallbackPath = fallbackPath || result . path ;
545+ if ( result && result . has ) {
546+ result . has = type ;
547+ ret . push ( result ) ;
548+ break ;
549+ }
550+ }
551+ }
552+
553+ // test global install.sh based D compilers
554+ let defaultDir = getDefaultInstallShDir ( ) ;
555+ if ( defaultDir ) {
556+ await new Promise ( ( resolve ) => {
557+ fs . readdir ( defaultDir ! , async ( err , files ) => {
558+ try {
559+ if ( err )
560+ return ;
561+ for ( let i = 0 ; i < files . length ; i ++ ) {
562+ const file = files [ i ] ;
563+ const type = getCompilerTypeFromPrefix ( file ) ;
564+ if ( type )
565+ await testInstallShPath ( path . join ( defaultDir ! , file ) , type ) ;
566+ }
567+ } finally {
568+ resolve ( undefined ) ;
569+ }
570+ } ) ;
571+ } ) ;
572+ }
573+
574+ // test code-d install.sh based D compilers
575+ await new Promise ( ( resolve ) => {
576+ fs . readdir ( defaultDir = getLocalCompilersDir ( ) , async ( err , files ) => {
577+ try {
578+ if ( err )
579+ return ;
580+ for ( let i = 0 ; i < files . length ; i ++ ) {
581+ const file = files [ i ] ;
582+ const type = getCompilerTypeFromPrefix ( file ) ;
583+ if ( type )
584+ await testInstallShPath ( path . join ( defaultDir ! , file ) , type ) ;
585+ }
586+ } finally {
587+ resolve ( undefined ) ;
588+ }
589+ } ) ;
590+ } ) ;
591+
405592 if ( ret . length == 0 && fallbackPath )
406593 ret . push ( { has : false , path : fallbackPath } ) ;
407594 return ret ;
408595}
409596
597+ // compiler type by checking if the file/foldername starts with ldc/dmd/gdc
598+ function getCompilerTypeFromPrefix ( folderName : string ) : "ldc" | "dmd" | "gdc" | null {
599+ if ( folderName . startsWith ( "dmd" ) )
600+ return "dmd" ;
601+ else if ( folderName . startsWith ( "gdc" ) || folderName . startsWith ( "gcc" ) )
602+ return "gdc" ;
603+ else if ( folderName . startsWith ( "ldc" ) )
604+ return "ldc" ;
605+ else
606+ return null ;
607+ }
608+
609+ const activatePathEnvironmentRegex = process . platform == "win32"
610+ ? / ^ s e t \s + P A T H = " ? ( [ ^ % " ] + ) " ? / gim
611+ : / ^ (?: e x p o r t \s + ) ? P A T H = " ? ( [ ^ $ " ] + ) " ? / gm;
410612const gdcVersionRegex = / ^ g c c v e r s i o n \s + v ? ( \d + (?: \. \d + ) + ) / gm;
411613const gdcFeVersionRegex = / ^ v e r s i o n \s + v ? ( \d + (?: \. \d + ) + ) / gm;
412614const gdcImportPathRegex = / ^ i m p o r t p a t h \s * \[ \d + \] \s * = \s * ( .+ ) / gm;
@@ -512,13 +714,19 @@ async function checkCompiler(compiler: "dmd" | "ldc" | "ldc2" | "gdc" | "gcc", c
512714
513715let binExistsCache : { [ index : string ] : string | false } = { } ;
514716async function testBinExists ( binary : string ) : Promise < string | false > {
717+ // common bash install case for windows users
718+ const win32GitBashPath = "C:\\Program Files\\Git\\usr\\bin\\bash.exe" ;
515719 if ( binExistsCache [ binary ] !== undefined )
516720 return binExistsCache [ binary ] ;
517721
518722 try {
519723 let founds = await which ( binary , {
520724 all : true
521725 } ) ;
726+ if ( process . platform == "win32" && ( binary . toUpperCase ( ) == "BASH" || binary . toUpperCase ( ) == "BASH.EXE" ) ) {
727+ if ( fs . existsSync ( win32GitBashPath ) )
728+ return binExistsCache [ binary ] = win32GitBashPath ;
729+ }
522730 for ( let i = 0 ; i < founds . length ; i ++ ) {
523731 const found = founds [ i ] ;
524732
0 commit comments