fix(dev-infra): verify python3 is available before release (#41753)

Verify that the /usr/bin/python points to the python3 interpreter binary.

PR Close #41753
This commit is contained in:
Joey Perrott 2021-04-21 10:46:17 -07:00 committed by Jessica Janiuk
parent 8bfd9e886b
commit a4a55f0687
3 changed files with 81 additions and 6 deletions

View File

@ -5179,7 +5179,7 @@ const ReleaseBuildCommandModule = {
* output is captured and returned as resolution on completion. Depending on the chosen
* output mode, stdout/stderr output is also printed to the console, or only on error.
*
* @returns a Promise resolving with captured stdout on success. The promise
* @returns a Promise resolving with captured stdout and stderr on success. The promise
* rejects on command failure.
*/
function spawnWithDebugOutput(command, args, options) {
@ -5191,9 +5191,11 @@ function spawnWithDebugOutput(command, args, options) {
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: ['inherit', 'pipe', 'pipe'] }));
var logOutput = '';
var stdout = '';
var stderr = '';
// Capture the stdout separately so that it can be passed as resolve value.
// This is useful if commands return parsable stdout.
childProcess.stderr.on('data', function (message) {
stderr += message;
logOutput += message;
// If console output is enabled, print the message directly to the stderr. Note that
// we intentionally print all output to stderr as stdout should not be polluted.
@ -5218,7 +5220,7 @@ function spawnWithDebugOutput(command, args, options) {
// On success, resolve the promise. Otherwise reject with the captured stderr
// and stdout log output if the output mode was set to `silent`.
if (status === 0) {
resolve({ stdout: stdout });
resolve({ stdout: stdout, stderr: stderr });
}
else {
reject(outputMode === 'silent' ? logOutput : undefined);
@ -6636,7 +6638,8 @@ class ReleaseTool {
log(yellow(' Angular Dev-Infra release staging script'));
log(yellow('--------------------------------------------'));
log();
if (!(yield this._verifyNoUncommittedChanges()) || !(yield this._verifyRunningFromNextBranch())) {
if (!(yield this._verifyEnvironmentHasPython3Symlink()) ||
!(yield this._verifyNoUncommittedChanges()) || !(yield this._verifyRunningFromNextBranch())) {
return CompletionState.FATAL_ERROR;
}
if (!(yield this._verifyNpmLoginState())) {
@ -6712,6 +6715,39 @@ class ReleaseTool {
return true;
});
}
/**
* Verifies the current environment contains /usr/bin/python which points to the Python3
* interpreter. python is required by our tooling in bazel as it contains scripts setting
* `#! /usr/bin/env python`.
*
* @returns a boolean indicating success or failure.
*/
_verifyEnvironmentHasPython3Symlink() {
return tslib.__awaiter(this, void 0, void 0, function* () {
try {
const pyVersion = yield spawnWithDebugOutput('/usr/bin/python', ['--version'], { mode: 'silent' });
const version = pyVersion.stdout.trim() || pyVersion.stderr.trim();
if (version.startsWith('Python 3.')) {
debug(`Local python version: ${version}`);
return true;
}
error(red(`\`/usr/bin/python\` is currently symlinked to "${version}", please update`));
error(red(' the symlink to link instead to Python3'));
error();
error(red(' Googlers: please run the following command to symlink python to python3:'));
error(red(' sudo ln -s /usr/bin/python3 /usr/bin/python'));
return false;
}
catch (_a) {
error(red(' ✘ `/usr/bin/python` does not exist, please ensure `/usr/bin/python` is'));
error(red(' symlinked to Python3.'));
error();
error(red(' Googlers: please run the following command to symlink python to python3:'));
error(red(' sudo ln -s /usr/bin/python3 /usr/bin/python'));
}
return false;
});
}
/**
* Verifies that the next branch from the configured repository is checked out.
* @returns a boolean indicating success or failure.

View File

@ -7,10 +7,12 @@
*/
import {ListChoiceOptions, prompt} from 'inquirer';
import {spawnWithDebugOutput} from '../../utils/child-process';
import {GithubConfig} from '../../utils/config';
import {debug, error, info, log, promptConfirm, red, yellow} from '../../utils/console';
import {GitClient} from '../../utils/git/index';
import {exec} from '../../utils/shelljs';
import {ReleaseConfig} from '../config/index';
import {ActiveReleaseTrains, fetchActiveReleaseTrains, nextBranchName} from '../versioning/active-release-trains';
import {npmIsLoggedIn, npmLogin, npmLogout} from '../versioning/npm-publish';
@ -45,7 +47,8 @@ export class ReleaseTool {
log(yellow('--------------------------------------------'));
log();
if (!await this._verifyNoUncommittedChanges() || !await this._verifyRunningFromNextBranch()) {
if (!await this._verifyEnvironmentHasPython3Symlink() ||
!await this._verifyNoUncommittedChanges() || !await this._verifyRunningFromNextBranch()) {
return CompletionState.FATAL_ERROR;
}
@ -127,6 +130,38 @@ export class ReleaseTool {
return true;
}
/**
* Verifies the current environment contains /usr/bin/python which points to the Python3
* interpreter. python is required by our tooling in bazel as it contains scripts setting
* `#! /usr/bin/env python`.
*
* @returns a boolean indicating success or failure.
*/
private async _verifyEnvironmentHasPython3Symlink(): Promise<boolean> {
try {
const pyVersion =
await spawnWithDebugOutput('/usr/bin/python', ['--version'], {mode: 'silent'});
const version = pyVersion.stdout.trim() || pyVersion.stderr.trim();
if (version.startsWith('Python 3.')) {
debug(`Local python version: ${version}`);
return true;
}
error(red(`\`/usr/bin/python\` is currently symlinked to "${version}", please update`));
error(red(' the symlink to link instead to Python3'));
error();
error(red(' Googlers: please run the following command to symlink python to python3:'));
error(red(' sudo ln -s /usr/bin/python3 /usr/bin/python'));
return false;
} catch {
error(red(' ✘ `/usr/bin/python` does not exist, please ensure `/usr/bin/python` is'));
error(red(' symlinked to Python3.'));
error();
error(red(' Googlers: please run the following command to symlink python to python3:'));
error(red(' sudo ln -s /usr/bin/python3 /usr/bin/python'));
}
return false;
}
/**
* Verifies that the next branch from the configured repository is checked out.
* @returns a boolean indicating success or failure.

View File

@ -19,6 +19,8 @@ export interface SpawnedProcessOptions extends Omit<SpawnOptions, 'stdio'> {
export interface SpawnedProcessResult {
/** Captured stdout in string format. */
stdout: string;
/** Captured stderr in string format. */
stderr: string;
}
/**
@ -26,7 +28,7 @@ export interface SpawnedProcessResult {
* output is captured and returned as resolution on completion. Depending on the chosen
* output mode, stdout/stderr output is also printed to the console, or only on error.
*
* @returns a Promise resolving with captured stdout on success. The promise
* @returns a Promise resolving with captured stdout and stderr on success. The promise
* rejects on command failure.
*/
export function spawnWithDebugOutput(
@ -42,10 +44,12 @@ export function spawnWithDebugOutput(
spawn(command, args, {...options, shell: true, stdio: ['inherit', 'pipe', 'pipe']});
let logOutput = '';
let stdout = '';
let stderr = '';
// Capture the stdout separately so that it can be passed as resolve value.
// This is useful if commands return parsable stdout.
childProcess.stderr.on('data', message => {
stderr += message;
logOutput += message;
// If console output is enabled, print the message directly to the stderr. Note that
// we intentionally print all output to stderr as stdout should not be polluted.
@ -73,7 +77,7 @@ export function spawnWithDebugOutput(
// On success, resolve the promise. Otherwise reject with the captured stderr
// and stdout log output if the output mode was set to `silent`.
if (status === 0) {
resolve({stdout});
resolve({stdout, stderr});
} else {
reject(outputMode === 'silent' ? logOutput : undefined);
}