( global as any ) . pxt = pxt ; import * as nodeutil from './nodeutil' ; import * as fs from 'fs' ; import * as path from 'path' ; import * as child_process from 'child_process' ; import { promisify } from "util" ; import * as hid from './hid' ; import U = pxt . Util ; import Map = pxt . Map ; export interface BuildEngine { id : string ; updateEngineAsync : ( ) => Promise < void > ; setPlatformAsync : ( ) => Promise < void > ; buildAsync : ( ) => Promise < void > ; patchHexInfo : ( extInfo : pxtc . ExtensionInfo ) => pxtc . HexInfo ; prepBuildDirAsync : ( ) => Promise < void > ; buildPath : string ; appPath : string ; moduleConfig : string ; outputPath ? : string ; deployAsync ? : ( r : pxtc . CompileResult ) => Promise < void > ; } export interface TargetRuntime { includePath : string ; } interface BuildCache { sha ? : string ; modSha ? : string ; } function noopAsync ( ) { return Promise . resolve ( ) ; } export const buildEngines = { yotta : { id : "yotta" , updateEngineAsync : ( ) => runYottaAsync ( [ "update" ] ) , buildAsync : ( ) => runYottaAsync ( [ "build" ] ) , setPlatformAsync : ( ) => runYottaAsync ( [ "target" , pxt . appTarget . compileService . yottaTarget ] ) , patchHexInfo : patchYottaHexInfo , prepBuildDirAsync : noopAsync , buildPath : "built/yt" , moduleConfig : "module.json" , deployAsync : msdDeployCoreAsync , appPath : "source" } , dockeryotta : { id : "dockeryotta" , updateEngineAsync : ( ) => runDockerAsync ( [ "yotta" , "update" ] ) , buildAsync : ( ) => runDockerAsync ( [ "yotta" , "build" ] ) , setPlatformAsync : ( ) => runDockerAsync ( [ "yotta" , "target" , pxt . appTarget . compileService . yottaTarget ] ) , patchHexInfo : patchYottaHexInfo , prepBuildDirAsync : noopAsync , buildPath : "built/dockeryt" , moduleConfig : "module.json" , deployAsync : msdDeployCoreAsync , appPath : "source" } , platformio : { id : "platformio" , updateEngineAsync : noopAsync , buildAsync : ( ) => runPlatformioAsync ( [ "run" ] ) , setPlatformAsync : noopAsync , patchHexInfo : patchPioHexInfo , prepBuildDirAsync : noopAsync , buildPath : "built/pio" , moduleConfig : "platformio.ini" , deployAsync : platformioDeployAsync , appPath : "src" } , codal : { id : "codal" , updateEngineAsync : updateCodalBuildAsync , buildAsync : ( ) => runBuildCmdAsync ( "python" , "build.py" ) , setPlatformAsync : noopAsync , patchHexInfo : patchCodalHexInfo , prepBuildDirAsync : prepCodalBuildDirAsync , buildPath : "built/codal" , moduleConfig : "codal.json" , deployAsync : msdDeployCoreAsync , appPath : "pxtapp" } , dockercodal : { id : "dockercodal" , updateEngineAsync : updateCodalBuildAsync , buildAsync : ( ) => runDockerAsync ( [ "python" , "build.py" ] ) , setPlatformAsync : noopAsync , patchHexInfo : patchCodalHexInfo , prepBuildDirAsync : prepCodalBuildDirAsync , buildPath : "built/dockercodal" , moduleConfig : "codal.json" , deployAsync : msdDeployCoreAsync , appPath : "pxtapp" } , dockermake : { id : "dockermake" , updateEngineAsync : ( ) => runBuildCmdAsync ( nodeutil . addCmd ( "npm" ) , "install" ) , buildAsync : ( ) => runDockerAsync ( [ "make" , "-j8" ] ) , setPlatformAsync : noopAsync , patchHexInfo : patchDockermakeHexInfo , prepBuildDirAsync : noopAsync , buildPath : "built/dockermake" , moduleConfig : "package.json" , deployAsync : msdDeployCoreAsync , outputPath : "bld/pxt-app.elf" , appPath : "pxtapp" } , dockercross : { id : "dockercross" , updateEngineAsync : ( ) => runBuildCmdAsync ( nodeutil . addCmd ( "npm" ) , "install" ) , buildAsync : ( ) => runDockerAsync ( [ "make" ] ) , setPlatformAsync : noopAsync , patchHexInfo : patchDockerCrossHexInfo , prepBuildDirAsync : noopAsync , buildPath : "built/dockercross" , moduleConfig : "package.json" , deployAsync : noopAsync , appPath : "pxtapp" } , dockerespidf : { id : "dockerespidf" , updateEngineAsync : noopAsync , buildAsync : ( ) => runDockerAsync ( [ "make" ] ) , setPlatformAsync : noopAsync , patchHexInfo : patchDockerEspIdfHexInfo , prepBuildDirAsync : noopAsync , buildPath : "built/dockerespidf" , moduleConfig : "sdkconfig.defaults" , deployAsync : noopAsync , appPath : "main" } , cs : { id : "cs" , updateEngineAsync : noopAsync , buildAsync : ( ) => runBuildCmdAsync ( getCSharpCommand ( ) , "-t:library" , "-out:pxtapp.dll" , "lib.cs" ) , setPlatformAsync : noopAsync , patchHexInfo : patchCSharpDll , prepBuildDirAsync : noopAsync , buildPath : "built/cs" , moduleConfig : "module.json" , deployAsync : buildFinalCsAsync , appPath : "pxtapp" } , } ; export let thisBuild = buildEngines [ 'yotta' ] ; export function setThisBuild ( b ) { if ( pxt . appTarget . compileService . dockerImage && ! process . env [ "PXT_NODOCKER" ] ) { if ( b === buildEngines [ "codal" ] ) b = buildEngines [ "dockercodal" ] ; if ( b === buildEngines [ "yotta" ] ) b = buildEngines [ "dockeryotta" ] ; } pxt . debug ( `set build engine: ${ b . id }` ) ; thisBuild = b ; } function patchYottaHexInfo ( extInfo ) { let buildEngine = thisBuild ; let hexPath = buildEngine . buildPath + "/build/" + pxt . appTarget . compileService . yottaTarget + "/source/" + pxt . appTarget . compileService . yottaBinary ; return { hex : fs . readFileSync ( hexPath , "utf8" ) . split ( /\r?\n/ ) } ; } function patchCodalHexInfo ( extInfo ) { let bin = pxt . appTarget . compileService . codalBinary ; let hexPath = thisBuild . buildPath + "/build/" + bin + ".hex" ; return { hex : fs . readFileSync ( hexPath , "utf8" ) . split ( /\r?\n/ ) } ; } function patchDockermakeHexInfo ( extInfo ) { let hexPath = thisBuild . buildPath + "/bld/pxt-app.hex" ; return { hex : fs . readFileSync ( hexPath , "utf8" ) . split ( /\r?\n/ ) } ; } function patchDockerCrossHexInfo ( extInfo ) { let hexPath = thisBuild . buildPath + "/bld/all.tgz.b64" ; return { hex : fs . readFileSync ( hexPath , "utf8" ) . split ( /\r?\n/ ) } ; } function patchDockerEspIdfHexInfo ( extInfo ) { let hexPath = thisBuild . buildPath + "/build/pxtapp.b64" ; return { hex : fs . readFileSync ( hexPath , "utf8" ) . split ( /\r?\n/ ) } ; } function patchCSharpDll ( extInfo ) { let hexPath = thisBuild . buildPath + "/lib.cs" ; return { hex : [ fs . readFileSync ( hexPath , "utf8" ) ] } ; } function pioFirmwareHex ( ) { let buildEngine = buildEngines [ 'platformio' ] ; return buildEngine . buildPath + "/.pioenvs/myenv/firmware.hex" ; } function patchPioHexInfo ( extInfo ) { return { hex : fs . readFileSync ( pioFirmwareHex ( ) , "utf8" ) . split ( /\r?\n/ ) } ; } function platformioDeployAsync ( r ) { if ( pxt . appTarget . compile . useUF2 ) return msdDeployCoreAsync ( r ) ; else return platformioUploadAsync ( r ) ; } function platformioUploadAsync ( r ) { let buildEngine = buildEngines [ 'platformio' ] ; let prevHex = fs . readFileSync ( pioFirmwareHex ( ) ) ; fs . writeFileSync ( pioFirmwareHex ( ) , r . outfiles [ pxtc . BINARY_HEX ] ) ; return runPlatformioAsync ( [ "run" , "--target" , "upload" , "--target" , "nobuild" , "-v" ] ) . finally ( ( ) => { pxt . log ( 'Restoring ' + pioFirmwareHex ( ) ) ; fs . writeFileSync ( pioFirmwareHex ( ) , prevHex ) ; } ) ; } export function buildHexAsync ( buildEngine , mainPkg , extInfo , forceBuild ) { let tasks = Promise . resolve ( ) ; let buildCachePath = buildEngine . buildPath + "/buildcache.json" ; let buildCache = { } ; if ( fs . existsSync ( buildCachePath ) ) { buildCache = nodeutil . readJson ( buildCachePath ) ; } if ( ! forceBuild && ( buildCache . sha == extInfo . sha && ! process . env [ "PXT_RUNTIME_DEV" ] ) ) { pxt . debug ( "Skipping C++ build." ) ; return tasks ; } pxt . debug ( "writing build files to " + buildEngine . buildPath ) ; let allFiles = U . clone ( extInfo . generatedFiles ) ; U . jsonCopyFrom ( allFiles , extInfo . extensionFiles ) ; let writeFiles = ( ) => { for ( let f of nodeutil . allFiles ( buildEngine . buildPath + "/" + buildEngine . appPath , 8 , true ) ) { let bn = f . slice ( buildEngine . buildPath . length ) ; bn = bn . replace ( /\\/g , "/" ) . replace ( /^\// , "/" ) ; if ( U . startsWith ( bn , "/" + buildEngine . appPath + "/" ) && ! allFiles [ bn ] ) { pxt . log ( "removing stale " + bn ) ; fs . unlinkSync ( f ) ; } } U . iterMap ( allFiles , ( fn , v ) => { fn = buildEngine . buildPath + fn ; nodeutil . mkdirP ( path . dirname ( fn ) ) ; let existing = null ; if ( fs . existsSync ( fn ) ) existing = fs . readFileSync ( fn , "utf8" ) ; if ( existing !== v ) nodeutil . writeFileSync ( fn , v ) ; } ) ; } ; tasks = tasks . then ( buildEngine . prepBuildDirAsync ) . then ( writeFiles ) ; let saveCache = ( ) => fs . writeFileSync ( buildCachePath , JSON . stringify ( buildCache , null , 4 ) + "\n" ) ; let modSha = U . sha256 ( extInfo . generatedFiles [ "/" + buildEngine . moduleConfig ] ) ; let needDal = false ; if ( buildCache . modSha !== modSha || forceBuild ) { tasks = tasks . then ( buildEngine . setPlatformAsync ) . then ( buildEngine . updateEngineAsync ) . then ( ( ) => { buildCache . sha = "" ; buildCache . modSha = modSha ; saveCache ( ) ; needDal = true ; } ) ; } else { pxt . debug ( `Skipping C++ build update.` ) ; } tasks = tasks . then ( buildEngine . buildAsync ) . then ( ( ) => { buildCache . sha = extInfo . sha ; saveCache ( ) ; if ( needDal ) buildDalConst ( buildEngine , mainPkg , true ) ; } ) ; return tasks ; } function runYottaAsync ( args ) { let ypath = process . env [ "YOTTA_PATH" ] ; let ytCommand = "yotta" ; let env = U . clone ( process . env ) ; if ( /;[A-Z]:\\/ . test ( ypath ) ) { for ( let pp of ypath . split ( ";" ) ) { let q = path . join ( pp , "yotta.exe" ) ; if ( fs . existsSync ( q ) ) { ytCommand = q ; env [ "PATH" ] = env [ "PATH" ] + ";" + ypath ; break ; } } } pxt . log ( "*** " + ytCommand + " " + args . join ( " " ) ) ; let child = child_process . spawn ( "yotta" , args , { cwd : thisBuild . buildPath , stdio : "inherit" , env : env } ) ; return new Promise < void > ( ( resolve , reject ) => { child . on ( "close" , ( code ) => { if ( code === 0 ) resolve ( ) ; else reject ( new Error ( "yotta " + args . join ( " " ) + ": exit code " + code ) ) ; } ) ; } ) ; } function runPlatformioAsync ( args ) { pxt . log ( "*** platformio " + args . join ( " " ) ) ; let child = child_process . spawn ( "platformio" , args , { cwd : thisBuild . buildPath , stdio : "inherit" , env : process . env } ) ; return new Promise < void > ( ( resolve , reject ) => { child . on ( "close" , ( code ) => { if ( code === 0 ) resolve ( ) ; else reject ( new Error ( "platformio " + args . join ( " " ) + ": exit code " + code ) ) ; } ) ; } ) ; } function runDockerAsync ( args ) { if ( process . env [ "PXT_NODOCKER" ] == "force" ) { const cmd = args . shift ( ) ; return nodeutil . spawnAsync ( { cmd , args , cwd : thisBuild . buildPath } ) ; } else { let fullpath = process . cwd ( ) + "/" + thisBuild . buildPath + "/" ; let cs = pxt . appTarget . compileService ; let dargs = cs . dockerArgs || [ "-u" , "build" ] ; let mountArg = fullpath + ":/src" ; if ( process . platform == "darwin" ) mountArg += ":delegated" ; return nodeutil . spawnAsync ( { cmd : "docker" , args : [ "run" , "--rm" , "-v" , mountArg , "-w" , "/src" ] . concat ( dargs ) . concat ( [ cs . dockerImage ] ) . concat ( args ) , cwd : thisBuild . buildPath } ) ; } } let parseCppInt = pxt . cpp . parseCppInt ; export function codalGitAsync ( ... args ) { return nodeutil . spawnAsync ( { cmd : "git" , args : args , cwd : thisBuild . buildPath } ) ; } function prepCodalBuildDirAsync ( ) { if ( fs . existsSync ( thisBuild . buildPath + "/.git/config" ) ) return Promise . resolve ( ) ; let cs = pxt . appTarget . compileService ; let pkg = "https://github.com/" + cs . githubCorePackage ; nodeutil . mkdirP ( "built" ) ; return nodeutil . runGitAsync ( "clone" , pkg , thisBuild . buildPath ) . then ( ( ) => codalGitAsync ( "checkout" , cs . gittag ) ) ; } function runBuildCmdAsync ( cmd , ... args ) { return nodeutil . spawnAsync ( { cmd , args , cwd : thisBuild . buildPath , } ) ; } function updateCodalBuildAsync ( ) { let cs = pxt . appTarget . compileService ; return codalGitAsync ( "checkout" , cs . gittag ) . then ( ( ) => /v\d+/ . test ( cs . gittag ) ? Promise . resolve ( ) : codalGitAsync ( "pull" ) , e => codalGitAsync ( "checkout" , "master" ) . then ( ( ) => codalGitAsync ( "pull" ) ) ) . then ( ( ) => codalGitAsync ( "checkout" , cs . gittag ) ) ; } export function buildDalConst ( buildEngine , mainPkg , rebuild = false , create = false ) { const constName = "dal.d.ts" ; let constPath = constName ; const config = mainPkg && mainPkg . config ; const corePackage = config && config . dalDTS && config . dalDTS . corePackage ; if ( corePackage ) constPath = path . join ( corePackage , constName ) ; let vals = { } ; let done = { } ; let excludeSyms = [ ] ; function expandInt ( s ) { s = s . trim ( ) ; let existing = U . lookup ( vals , s ) ; if ( existing != null && existing != "?" ) s = existing ; let mm = /^\((.*)\)/ . exec ( s ) ; if ( mm ) s = mm [ 1 ] ; let m = /^(\w+)\s*([\+\|])\s*(.*)$/ . exec ( s ) ; if ( m ) { let k = expandInt ( m [ 1 ] ) ; if ( k != null ) return m [ 2 ] == "+" ? k + expandInt ( m [ 3 ] ) : k | expandInt ( m [ 3 ] ) ; } let pp = parseCppInt ( s ) ; if ( pp != null ) return pp ; return null ; } function extractConstants ( fileName , src , dogenerate = false ) { let lineNo = 0 ; let outp = "" ; let inEnum = false ; let enumVal = 0 ; let defineVal = ( n , v ) => { if ( excludeSyms . some ( s => U . startsWith ( n , s ) ) ) return ; let parsed = expandInt ( v ) ; if ( parsed != null ) { v = parsed . toString ( ) ; let curr = U . lookup ( vals , n ) ; if ( curr == null || curr == v ) { vals [ n ] = v ; if ( dogenerate && ! done [ n ] ) { outp += `    ${ n } = ${ v },\n` ; done [ n ] = v ; } } else { vals [ n ] = "?" ; if ( dogenerate && ! /^MICROBIT_DISPLAY_(ROW|COLUMN)_COUNT|PXT_VTABLE_SHIFT$/ . test ( n ) ) pxt . log ( `${ fileName }(${ lineNo }): #define conflict, ${ n }` ) ; } } } ; src . split ( /\r?\n/ ) . forEach ( ln => { ++ lineNo ; ln = ln . replace ( /\/\/.*/ , "" ) . replace ( /\/\*.*/g , "" ) ; let m = /^\s*#define\s+(\w+)\s+(.*)$/ . exec ( ln ) ; if ( m ) { defineVal ( m [ 1 ] , m [ 2 ] ) ; } if ( inEnum && /}/ . test ( ln ) ) inEnum = false ; if ( /^\s*enum\s+(\w+)/ . test ( ln ) ) { inEnum = true ; enumVal = - 1 ; } const shouldExpand = inEnum && ( m = /^\s*(\w+)\s*(=\s*(.*?))?,?\s*$/ . exec ( ln ) ) ; if ( shouldExpand ) { let v = m [ 3 ] ; if ( v ) { enumVal = expandInt ( v ) ; if ( enumVal == null ) { pxt . log ( `${ fileName }(${ lineNo }): invalid enum initializer, ${ ln }` ) ; inEnum = false ; return ; } } else { enumVal ++ ; v = enumVal + "" ; } defineVal ( m [ 1 ] , v ) ; } } ) ; return outp ; } if ( mainPkg && ( create || ( mainPkg . getFiles ( ) . indexOf ( constName ) >= 0 && ( rebuild || ! fs . existsSync ( constName ) ) ) ) ) { pxt . log ( `rebuilding ${ constName } into ${ constPath }...` ) ; let files = [ ] ; let foundConfig = false ; for ( let d of mainPkg . sortedDeps ( ) ) { if ( d . config . dalDTS ) { if ( d . config . dalDTS . includeDirs ) for ( let dn of d . config . dalDTS . includeDirs ) { dn = buildEngine . buildPath + "/" + dn ; if ( U . endsWith ( dn , ".h" ) ) files . push ( dn ) ; else { let here = nodeutil . allFiles ( dn , 20 ) . filter ( fn => U . endsWith ( fn , ".h" ) ) ; U . pushRange ( files , here ) ; } } excludeSyms = d . config . dalDTS . excludePrefix || excludeSyms ; foundConfig = true ; } } if ( ! foundConfig ) { let incPath = buildEngine . buildPath + "/yotta_modules/microbit-dal/inc/" ; if ( ! fs . existsSync ( incPath ) ) incPath = buildEngine . buildPath + "/yotta_modules/codal/inc/" ; if ( ! fs . existsSync ( incPath ) ) incPath = buildEngine . buildPath ; if ( ! fs . existsSync ( incPath ) ) U . userError ( "cannot find " + incPath ) ; files = nodeutil . allFiles ( incPath , 20 ) . filter ( fn => U . endsWith ( fn , ".h" ) ) . filter ( fn => fn . indexOf ( "/mbed-classic/" ) < 0 ) . filter ( fn => fn . indexOf ( "/mbed-os/" ) < 0 ) ; } files . sort ( U . strcmp ) ; let fc = { } ; for ( let fn of files ) { if ( U . endsWith ( fn , "Config.h" ) ) continue ; fc [ fn ] = fs . readFileSync ( fn , "utf8" ) ; } files = Object . keys ( fc ) ; for ( let fn of files ) { extractConstants ( fn , fc [ fn ] ) ; } for ( let fn of files ) { extractConstants ( fn , fc [ fn ] ) ; } let consts = "// Auto-generated. Do not edit.\ndeclare const enum DAL {\n" ; for ( let fn of files ) { let v = extractConstants ( fn , fc [ fn ] , true ) ; if ( v ) { consts += "    // " + fn . replace ( /\\/g , "/" ) . replace ( buildEngine . buildPath , "" ) + "\n" ; consts += v ; } } consts += "}\n" ; fs . writeFileSync ( constPath , consts ) ; } } const writeFileAsync = promisify ( fs . writeFile ) ; const cpExecAsync = promisify ( child_process . exec ) ; const readDirAsync = promisify ( fs . readdir ) ; function buildFinalCsAsync ( res ) { return nodeutil . spawnAsync ( { cmd : getCSharpCommand ( ) , args : [ "-out:pxtapp.exe" , "binary.cs" ] , cwd : "built" , } ) ; } function getCSharpCommand ( ) { return process . platform == "win32" ? "mcs.bat" : "mcs" ; } function msdDeployCoreAsync ( res ) { const firmwareName = [ pxtc . BINARY_UF2 , pxtc . BINARY_HEX , pxtc . BINARY_ELF ] . filter ( f => ! ! res . outfiles [ f ] ) [ 0 ] ; if ( ! firmwareName ) { pxt . reportError ( "compile" , `firmware missing from built files (${ Object . keys ( res . outfiles ) . join ( ', ' ) })` ) ; return Promise . resolve ( ) ; } const firmware = res . outfiles [ firmwareName ] ; const encoding = firmwareName == pxtc . BINARY_HEX ? "utf8" : "base64" ; function copyDeployAsync ( ) { return getBoardDrivesAsync ( ) . then ( drives => filterDrives ( drives ) ) . then ( drives => { if ( drives . length == 0 ) throw new Error ( "cannot find any drives to deploy to" ) ; pxt . log ( `copying ${ firmwareName } to ` + drives . join ( ", " ) ) ; const writeHexFile = ( drivename ) => { return writeFileAsync ( path . join ( drivename , firmwareName ) , firmware , encoding ) . then ( ( ) => pxt . debug ( "   wrote to " + drivename ) ) . catch ( ( e ) => { throw new Error ( `failed writing to ${ drivename }; ${ e . message }` ) ; } ) ; } ; return U . promiseMapAll ( drives , d => writeHexFile ( d ) ) . then ( ( ) => drives . length ) ; } ) . then ( ( ) => { } ) ; } function hidDeployAsync ( ) { const f = firmware ; const blocks = pxtc . UF2 . parseFile ( U . stringToUint8Array ( atob ( f ) ) ) ; return hid . initAsync ( ) . then ( dev => dev . flashAsync ( blocks ) ) ; } let p = Promise . resolve ( ) ; if ( pxt . appTarget . compile && pxt . appTarget . compile . useUF2 && ! pxt . appTarget . serial . noDeploy && hid . isInstalled ( true ) ) { p = p . then ( ( ) => hidDeployAsync ( ) ) . catch ( e => copyDeployAsync ( ) ) ; } else { p = p . then ( ( ) => copyDeployAsync ( ) ) ; } return p ; } function getBoardDrivesAsync ( ) { if ( process . platform == "win32" ) { const rx = new RegExp ( "^([A-Z]:)\\s+(\\d+).* " + pxt . appTarget . compile . deployDrives ) ; return cpExecAsync ( "wmic PATH Win32_LogicalDisk get DeviceID, VolumeName, FileSystem, DriveType" ) . then ( ( { stdout , stderr } ) => { let res = [ ] ; stdout . split ( /\n/ ) . forEach ( ln => { let m = rx . exec ( ln ) ; if ( m && m [ 2 ] == "2" ) { res . push ( m [ 1 ] + "/" ) ; } } ) ; return res ; } ) ; } else if ( process . platform == "darwin" ) { const rx = new RegExp ( pxt . appTarget . compile . deployDrives ) ; return readDirAsync ( "/Volumes" ) . then ( lst => lst . filter ( s => rx . test ( s ) ) . map ( s => "/Volumes/" + s + "/" ) ) ; } else if ( process . platform == "linux" ) { const rx = new RegExp ( pxt . appTarget . compile . deployDrives ) ; const user = process . env [ "USER" ] ; if ( nodeutil . existsDirSync ( `/media/${ user }` ) ) return readDirAsync ( `/media/${ user }` ) . then ( lst => lst . filter ( s => rx . test ( s ) ) . map ( s => `/media/${ user }/${ s }/` ) ) ; return Promise . resolve ( [ ] ) ; } else { return Promise . resolve ( [ ] ) ; } } function filterDrives ( drives ) { const marker = pxt . appTarget . compile . deployFileMarker ; if ( ! marker ) return drives ; return drives . filter ( d => { try { return fs . existsSync ( path . join ( d , marker ) ) ; } catch ( e ) { return false ; } } ) ; }