Skip to content

Commit 0c26fea

Browse files
committed
Switch to Python 3.7 is a default installer for Windows
1 parent 5402d04 commit 0c26fea

File tree

3 files changed

+144
-78
lines changed

3 files changed

+144
-78
lines changed

src/installer/helpers.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,12 @@ function getContentLength(url) {
9595
url
9696
},
9797
(err, response) => {
98-
if (err || response.statusCode !== 200 || !response.headers['content-length']) {
98+
if (
99+
err ||
100+
response.statusCode !== 200 ||
101+
!response.headers ||
102+
!response.headers['content-length']
103+
) {
99104
resolve(-1);
100105
}
101106
resolve(parseInt(response.headers['content-length']));

src/installer/stages/platformio-core.js

Lines changed: 136 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ import path from 'path';
1616
import semver from 'semver';
1717
import tmp from 'tmp';
1818

19-
2019
export 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
}

src/misc.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ export async function getPythonExecutable(
160160
locations.push(getEnvDir()); // conda
161161
}
162162
if (IS_WINDOWS) {
163-
// isolated Python 2.7 in PlatformIO Home directory
164-
locations.push(path.join(getHomeDir(), 'python27'));
163+
// isolated Python 3.7 in PlatformIO Home directory
164+
locations.push(path.join(getHomeDir(), 'python37'));
165165
}
166166
// extend with paths from env.PATH
167167
process.env.PATH.split(path.delimiter).forEach(item => {

0 commit comments

Comments
 (0)