@@ -16,15 +16,18 @@ import path from 'path';
1616import semver from 'semver' ;
1717import tmp from 'tmp' ;
1818
19-
2019export default class PlatformIOCoreStage extends BaseStage {
21-
2220 static UPGRADE_PIOCORE_TIMEOUT = 86400 * 7 * 1000 ; // 7 days
21+ static PENV_LOCK_FILE_NAME = 'piopenv.lock' ;
22+ static PENV_LOCK_VERSION = 1 ; // only integer is valid
2323
24- static pythonVersion = '2.7.13' ;
25- static pipUrl = 'https://files.pythonhosted.org/packages/45/ae/8a0ad77defb7cc903f09e551d88b443304a9bd6e6f124e75c0fbbf6de8f7/pip-18.1.tar.gz' ;
26- static virtualenvUrl = 'https://files.pythonhosted.org/packages/4e/8b/75469c270ac544265f0020aa7c4ea925c5284b23e445cf3aa8b99f662690/virtualenv-16.1.0.tar.gz' ;
27- static pioCoreDevelopUrl = 'https://github.com/platformio/platformio/archive/develop.zip' ;
24+ static pythonVersion = '3.7.4' ;
25+ static pipUrl =
26+ 'https://files.pythonhosted.org/packages/00/9e/4c83a0950d8bdec0b4ca72afd2f9cea92d08eb7c1a768363f2ea458d08b4/pip-19.2.3.tar.gz' ;
27+ static virtualenvUrl =
28+ 'https://files.pythonhosted.org/packages/66/f0/6867af06d2e2f511e4e1d7094ff663acdebc4f15d4a0cb0fed1007395124/virtualenv-16.7.5.tar.gz' ;
29+ static pioCoreDevelopUrl =
30+ 'https://github.com/platformio/platformio/archive/develop.zip' ;
2831
2932 constructor ( ) {
3033 super ( ...arguments ) ;
@@ -38,7 +41,9 @@ export default class PlatformIOCoreStage extends BaseStage {
3841 async whereIsPython ( ) {
3942 let status = this . params . pythonPrompt . STATUS_TRY_AGAIN ;
4043 do {
41- const pythonExecutable = await misc . getPythonExecutable ( this . params . useBuiltinPIOCore ) ;
44+ const pythonExecutable = await misc . getPythonExecutable (
45+ this . params . useBuiltinPIOCore
46+ ) ;
4247 if ( pythonExecutable ) {
4348 return pythonExecutable ;
4449 }
@@ -59,70 +64,84 @@ export default class PlatformIOCoreStage extends BaseStage {
5964 } while ( status !== this . params . pythonPrompt . STATUS_ABORT ) ;
6065
6166 this . status = BaseStage . STATUS_FAILED ;
62- throw new Error ( 'Can not find Python Interpreter' ) ;
67+ throw new Error (
68+ 'Can not find Python Interpreter. Please install Python 3.5 or above manually'
69+ ) ;
6370 }
6471
6572 async installPythonForWindows ( ) {
66- // https://www.python.org/ftp/python/2 .7.14 /python-2 .7.14.msi
67- // https://www.python.org/ftp/python/2 .7.14 /python-2 .7.14. amd64.msi
68- const pythonArch = process . arch === 'x64' ? '. amd64' : '' ;
69- const msiUrl = `https://www.python.org/ftp/python/${ PlatformIOCoreStage . pythonVersion } /python-${ PlatformIOCoreStage . pythonVersion } ${ pythonArch } .msi ` ;
70- const msiInstaller = await helpers . download (
71- msiUrl ,
72- path . join ( core . getCacheDir ( ) , path . basename ( msiUrl ) )
73+ // https://www.python.org/ftp/python/3 .7.4 /python-3 .7.4.exe
74+ // https://www.python.org/ftp/python/3 .7.4 /python-3 .7.4- amd64.exe
75+ const pythonArch = process . arch === 'x64' ? '- amd64' : '' ;
76+ const installerUrl = `https://www.python.org/ftp/python/${ PlatformIOCoreStage . pythonVersion } /python-${ PlatformIOCoreStage . pythonVersion } ${ pythonArch } .exe ` ;
77+ const installer = await helpers . download (
78+ installerUrl ,
79+ path . join ( core . getCacheDir ( ) , path . basename ( installerUrl ) )
7380 ) ;
74- const targetDir = path . join ( core . getHomeDir ( ) , 'python27 ' ) ;
81+ const targetDir = path . join ( core . getHomeDir ( ) , 'python37 ' ) ;
7582 const pythonPath = path . join ( targetDir , 'python.exe' ) ;
7683
7784 if ( ! fs . isFileSync ( pythonPath ) ) {
78- try {
79- await this . installPythonFromWindowsMSI ( msiInstaller , targetDir ) ;
80- } catch ( err ) {
81- console . warn ( err ) ;
82- await this . installPythonFromWindowsMSI ( msiInstaller , targetDir , true ) ;
83- }
85+ await this . installPythonFromWindowsInstaller ( installer , targetDir ) ;
8486 }
8587
8688 // append temporary to system environment
87- process . env . PATH = [ targetDir , path . join ( targetDir , 'Scripts' ) , process . env . PATH ] . join ( path . delimiter ) ;
89+ process . env . PATH = [
90+ targetDir ,
91+ path . join ( targetDir , 'Scripts' ) ,
92+ process . env . PATH
93+ ] . join ( path . delimiter ) ;
8894 process . env . Path = process . env . PATH ;
89-
90- // install virtualenv
91- return new Promise ( resolve => {
92- misc . runCommand (
93- 'pip' ,
94- [ 'install' , 'virtualenv' ] ,
95- ( ) => resolve ( pythonPath )
96- ) ;
97- } ) ;
95+ return pythonPath ;
9896 }
9997
100- async installPythonFromWindowsMSI ( msiInstaller , targetDir , administrative = false ) {
101- const logFile = path . join ( core . getCacheDir ( ) , 'python27msi.log' ) ;
102- await new Promise ( ( resolve , reject ) => {
103- misc . runCommand (
104- 'msiexec.exe' ,
105- [ administrative ? '/a' : '/i' , `"${ msiInstaller } "` , '/qn' , '/li' , `"${ logFile } "` , `TARGETDIR="${ targetDir } "` ] ,
106- ( code , stdout , stderr ) => {
107- if ( code === 0 ) {
108- return resolve ( stdout ) ;
109- } else {
110- if ( fs . isFileSync ( logFile ) ) {
111- stderr = fs . readFileSync ( logFile ) . toString ( ) ;
112- }
113- return reject ( new Error ( `MSI Python2.7: ${ stderr } ` ) ) ;
114- }
115- } ,
116- {
117- spawnOptions : {
118- shell : true
119- }
98+ async installPythonFromWindowsInstaller ( installer , targetDir ) {
99+ if ( fs . isDirectorySync ( targetDir ) ) {
100+ try {
101+ fs . removeSync ( targetDir ) ;
102+ } catch ( err ) {
103+ console . warn ( err ) ;
104+ }
105+ }
106+ misc . runCommand (
107+ installer ,
108+ [
109+ '/quiet' ,
110+ '/log' ,
111+ path . join ( core . getCacheDir ( ) , 'python-installer.log' ) ,
112+ 'SimpleInstall=1' ,
113+ 'InstallAllUsers=0' ,
114+ 'InstallLauncherAllUsers=0' ,
115+ 'Shortcuts=0' ,
116+ 'Include_lib=1' ,
117+ 'Include_pip=1' ,
118+ 'Include_doc=0' ,
119+ 'Include_launcher=0' ,
120+ 'Include_test=0' ,
121+ 'Include_tcltk=0' ,
122+ `DefaultJustForMeTargetDir=${ targetDir } `
123+ ] ,
124+ {
125+ spawnOptions : {
126+ shell : true
120127 }
121- ) ;
122- } ) ;
123- if ( ! fs . isFileSync ( path . join ( targetDir , 'python.exe' ) ) ) {
124- throw new Error ( 'Could not install Python 2.7 using MSI' ) ;
128+ }
129+ ) ;
130+
131+ const timeout = 5 * 60 ;
132+ const delay = 5 ;
133+ let elapsed = 0 ;
134+ const pipPath = path . join ( targetDir , 'Scripts' , 'pip.exe' ) ;
135+ while ( elapsed < timeout ) {
136+ await misc . sleep ( delay * 1000 ) ;
137+ elapsed += delay ;
138+ if ( fs . isFileSync ( pipPath ) ) {
139+ return true ;
140+ }
125141 }
142+ throw new Error (
143+ 'Could not install Python 3 automatically. Please install it manually from https://python.org'
144+ ) ;
126145 }
127146
128147 cleanVirtualEnvDir ( ) {
@@ -163,6 +182,7 @@ export default class PlatformIOCoreStage extends BaseStage {
163182 }
164183
165184 async createVirtualenvWithLocal ( ) {
185+ this . cleanVirtualEnvDir ( ) ;
166186 const pythonExecutable = await this . whereIsPython ( ) ;
167187 const venvCmdOptions = [
168188 [ pythonExecutable , '-m' , 'venv' , core . getEnvDir ( ) ] ,
@@ -177,9 +197,12 @@ export default class PlatformIOCoreStage extends BaseStage {
177197 try {
178198 return await new Promise ( ( resolve , reject ) => {
179199 misc . runCommand (
180- cmdOptions [ 0 ] , cmdOptions . slice ( 1 ) ,
200+ cmdOptions [ 0 ] ,
201+ cmdOptions . slice ( 1 ) ,
181202 ( code , stdout , stderr ) => {
182- return code === 0 ? resolve ( stdout ) : reject ( new Error ( `User's Virtualenv: ${ stderr } ` ) ) ;
203+ return code === 0
204+ ? resolve ( stdout )
205+ : reject ( new Error ( `User's Virtualenv: ${ stderr } ` ) ) ;
183206 }
184207 ) ;
185208 } ) ;
@@ -203,8 +226,9 @@ export default class PlatformIOCoreStage extends BaseStage {
203226 unsafeCleanup : true
204227 } ) . name ;
205228 const dstDir = await helpers . extractTarGz ( archivePath , tmpDir ) ;
206- const virtualenvScript = fs . listTreeSync ( dstDir ) . find (
207- item => path . basename ( item ) === 'virtualenv.py' ) ;
229+ const virtualenvScript = fs
230+ . listTreeSync ( dstDir )
231+ . find ( item => path . basename ( item ) === 'virtualenv.py' ) ;
208232 if ( ! virtualenvScript ) {
209233 throw new Error ( 'Can not find virtualenv.py script' ) ;
210234 }
@@ -268,7 +292,9 @@ export default class PlatformIOCoreStage extends BaseStage {
268292 } catch ( errPkg ) {
269293 misc . reportError ( errDl ) ;
270294 console . warn ( errPkg ) ;
271- throw new Error ( `Could not create PIO Core Virtual Environment. Please create it manually -> http://bit.ly/pio-core-virtualenv \n ${ errDl . toString ( ) } ` ) ;
295+ throw new Error (
296+ `Could not create PIO Core Virtual Environment. Please create it manually -> http://bit.ly/pio-core-virtualenv \n ${ errDl . toString ( ) } `
297+ ) ;
272298 }
273299 }
274300 }
@@ -281,9 +307,13 @@ export default class PlatformIOCoreStage extends BaseStage {
281307 path . join ( core . getCacheDir ( ) , path . basename ( PlatformIOCoreStage . pipUrl ) )
282308 ) ;
283309 return new Promise ( ( resolve , reject ) => {
284- misc . runCommand ( pythonExecutable , [ '-m' , 'pip' , 'install' , '-U' , pipArchive ] , ( code , stdout , stderr ) => {
285- return code === 0 ? resolve ( stdout ) : reject ( stderr ) ;
286- } ) ;
310+ misc . runCommand (
311+ pythonExecutable ,
312+ [ '-m' , 'pip' , 'install' , '-U' , pipArchive ] ,
313+ ( code , stdout , stderr ) => {
314+ return code === 0 ? resolve ( stdout ) : reject ( stderr ) ;
315+ }
316+ ) ;
287317 } ) ;
288318 }
289319
@@ -348,14 +378,23 @@ export default class PlatformIOCoreStage extends BaseStage {
348378 const newState = this . initState ( ) ;
349379 const now = new Date ( ) . getTime ( ) ;
350380 if (
351- ( process . env . PLATFORMIO_IDE && newState . lastIDEVersion && newState . lastIDEVersion !== process . env . PLATFORMIO_IDE )
352- || ( ( now - PlatformIOCoreStage . UPGRADE_PIOCORE_TIMEOUT ) > parseInt ( newState . pioCoreChecked ) )
381+ ( process . env . PLATFORMIO_IDE &&
382+ newState . lastIDEVersion &&
383+ newState . lastIDEVersion !== process . env . PLATFORMIO_IDE ) ||
384+ now - PlatformIOCoreStage . UPGRADE_PIOCORE_TIMEOUT >
385+ parseInt ( newState . pioCoreChecked )
353386 ) {
354387 newState . pioCoreChecked = now ;
355388 // PIO Core
356389 await new Promise ( resolve => {
357390 core . runPIOCommand (
358- [ 'upgrade' , ...( this . params . useDevelopmentPIOCore && ! semver . prerelease ( currentCoreVersion ) ? [ '--dev' ] : [ ] ) ] ,
391+ [
392+ 'upgrade' ,
393+ ...( this . params . useDevelopmentPIOCore &&
394+ ! semver . prerelease ( currentCoreVersion )
395+ ? [ '--dev' ]
396+ : [ ] )
397+ ] ,
359398 ( code , stdout , stderr ) => {
360399 if ( code !== 0 ) {
361400 console . warn ( stdout , stderr ) ;
@@ -369,16 +408,35 @@ export default class PlatformIOCoreStage extends BaseStage {
369408 this . state = newState ;
370409 }
371410
411+ checkEnvDirLock ( ) {
412+ if ( ! fs . isDirectorySync ( core . getEnvBinDir ( ) ) ) {
413+ throw new Error ( 'Virtual environment is not created' ) ;
414+ }
415+ const lockPath = path . join (
416+ core . getEnvDir ( ) ,
417+ PlatformIOCoreStage . PENV_LOCK_FILE_NAME
418+ ) ;
419+ if ( ! fs . isFileSync ( lockPath ) ) {
420+ throw new Error ( 'Virtual environment lock file is missed' ) ;
421+ }
422+ if ( parseInt ( fs . readFileSync ( lockPath ) ) !== PlatformIOCoreStage . PENV_LOCK_VERSION ) {
423+ throw new Error ( 'Virtual environment is outdated' ) ;
424+ }
425+ return true ;
426+ }
427+
428+ setEnvDirLock ( ) {
429+ fs . writeFileSync (
430+ path . join ( core . getEnvDir ( ) , PlatformIOCoreStage . PENV_LOCK_FILE_NAME ) ,
431+ PlatformIOCoreStage . PENV_LOCK_VERSION . toString ( )
432+ ) ;
433+ }
434+
372435 async check ( ) {
373436 const coreVersion = helpers . PEPverToSemver ( await core . getVersion ( ) ) ;
374437
375438 if ( this . params . useBuiltinPIOCore ) {
376- if ( ! fs . isDirectorySync ( core . getEnvBinDir ( ) ) ) {
377- throw new Error ( 'Virtual environment is not created' ) ;
378- }
379- else if ( semver . lt ( coreVersion , '3.5.0-rc.4' ) ) {
380- throw new Error ( 'Force new python environment' ) ;
381- }
439+ this . checkEnvDirLock ( ) ;
382440 try {
383441 await this . autoUpgradePIOCore ( coreVersion ) ;
384442 } catch ( err ) {
@@ -389,7 +447,9 @@ export default class PlatformIOCoreStage extends BaseStage {
389447 if ( semver . lt ( coreVersion , this . params . pioCoreMinVersion ) ) {
390448 this . params . setUseBuiltinPIOCore ( true ) ;
391449 this . params . useBuiltinPIOCore = true ;
392- this . params . useDevelopmentPIOCore = this . params . useDevelopmentPIOCore || semver . prerelease ( this . params . pioCoreMinVersion ) ;
450+ this . params . useDevelopmentPIOCore =
451+ this . params . useDevelopmentPIOCore ||
452+ semver . prerelease ( this . params . pioCoreMinVersion ) ;
393453 throw new Error ( `Incompatible PIO Core ${ coreVersion } ` ) ;
394454 }
395455
@@ -410,6 +470,8 @@ export default class PlatformIOCoreStage extends BaseStage {
410470
411471 try {
412472 await this . createVirtualenv ( ) ;
473+ this . setEnvDirLock ( ) ;
474+
413475 await this . installPIOCore ( ) ;
414476 await this . installPIOHome ( ) ;
415477 } catch ( err ) {
@@ -420,5 +482,4 @@ export default class PlatformIOCoreStage extends BaseStage {
420482 this . status = BaseStage . STATUS_SUCCESSED ;
421483 return true ;
422484 }
423-
424485}
0 commit comments