diff --git a/dev-infra/ng-dev.js b/dev-infra/ng-dev.js index 3c39c13097..931f4b5962 100755 --- a/dev-infra/ng-dev.js +++ b/dev-infra/ng-dev.js @@ -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. diff --git a/dev-infra/release/publish/index.ts b/dev-infra/release/publish/index.ts index 2f6e14552e..03b9e360ef 100644 --- a/dev-infra/release/publish/index.ts +++ b/dev-infra/release/publish/index.ts @@ -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 { + 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. diff --git a/dev-infra/utils/child-process.ts b/dev-infra/utils/child-process.ts index b2a4c21df7..07bddf9da5 100644 --- a/dev-infra/utils/child-process.ts +++ b/dev-infra/utils/child-process.ts @@ -19,6 +19,8 @@ export interface SpawnedProcessOptions extends Omit { 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); }