HEX
Server: Apache/2.4.65 (Ubuntu)
System: Linux ielts-store-v2 6.8.0-1036-gcp #38~22.04.1-Ubuntu SMP Thu Aug 14 01:19:18 UTC 2025 x86_64
User: root (0)
PHP: 7.2.34-54+ubuntu20.04.1+deb.sury.org+1
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,
Upload Files
File: //snap/google-cloud-cli/396/lib/googlecloudsdk/core/updater/python_manager.py
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Python installers for gcloud."""

import os
import sys

from googlecloudsdk.core import config
from googlecloudsdk.core import execution_utils
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
from googlecloudsdk.core.util import platforms


PYTHON_VERSION = '3.12'
PYTHON_VERSION_INFO = (3, 12)
MACOS_PYTHON = 'python-3.12.8-macos11.tar.gz'

HOMEBREW_BIN = '/opt/homebrew/bin'
MACOS_PYTHON_INSTALL_PATH = (
    f'/Library/Frameworks/Python.framework/Versions/{PYTHON_VERSION}/')
MACOS_PYTHON_URL = (
    'https://dl.google.com/dl/cloudsdk/channels/rapid/' + MACOS_PYTHON
)


def _VirtualEnvPath():
  env_dir = config.Paths().virtualenv_dir
  if os.path.isdir(env_dir):
    return env_dir
  else:
    return None


def _CreateVirtualEnv(cli, python_to_use):
  cli.Execute(['config', 'virtualenv', 'create', '--python-to-use',
               python_to_use])


def _RecreateVirtualEnv(cli, python_to_use, existing_env_dir):
  print(f'Virtual env already exists at {existing_env_dir}. '
        'Deleting so we can create new one.')
  cli.Execute(['config', 'virtualenv', 'delete'])
  _CreateVirtualEnv(cli, python_to_use)


def _UpdateVirtualEnv(cli):
  cli.Execute(['config', 'virtualenv', 'update'])


def _EnableVirtualEnv(cli):
  cli.Execute(['config', 'virtualenv', 'enable'])


def UpdatePythonDependencies(python_to_use):
  """Enables virtual environment with new python version and dependencies."""
  try:
    from googlecloudsdk import gcloud_main  # pylint: disable=g-import-not-at-top
    cli = gcloud_main.CreateCLI([])

    # Assume we are executing in a virtual environment if env_dir exists
    env_dir = _VirtualEnvPath()
    if env_dir and sys.version_info[:2] != PYTHON_VERSION_INFO:
      _RecreateVirtualEnv(cli, python_to_use, env_dir)
    elif env_dir:
      _UpdateVirtualEnv(cli)
    else:
      _CreateVirtualEnv(cli, python_to_use)

    _EnableVirtualEnv(cli)
  except ImportError:
    print('Failed to enable virtual environment')


def _IsHomebrewInstalled():
  return os.path.isdir(HOMEBREW_BIN) and 'homebrew' in config.GcloudPath()


def _PromptPythonUpdate(python_install_path):
  return (
      f'(Recommended) Python {PYTHON_VERSION} installation detected in '
      f'{python_install_path}, install required modules (Y/n)?')


def _PromptPythonInstall():
  if _IsHomebrewInstalled():
    return f'Homebrew install Python {PYTHON_VERSION}?'
  else:
    return f'Download and run Python {PYTHON_VERSION} installer?'


def _BrewInstallPython():
  """Make sure python version is correct for user using gcloud with homebrew."""
  brew_install = f'{HOMEBREW_BIN}/brew install python@{PYTHON_VERSION}'
  print(f'Running "{brew_install}".')

  exit_code = execution_utils.Exec(brew_install.split(' '), no_exit=True)
  if exit_code != 0:
    return (
        f'"{brew_install}" failed. Please brew install '
        f'python@{PYTHON_VERSION} manually.')
  return None


def _MacInstallPython():
  """Optionally install Python on Mac machines."""

  print(f'Running Python {PYTHON_VERSION} installer, you may be prompted for '
        'sudo password...')

  # Xcode Command Line Tools is required to install Python.
  PromptAndInstallXcodeCommandLineTools()

  with files.TemporaryDirectory() as tempdir:
    with files.ChDir(tempdir):
      curl_args = ['curl', '--silent', '-O', MACOS_PYTHON_URL]
      exit_code = execution_utils.Exec(curl_args, no_exit=True)
      if exit_code != 0:
        return 'Failed to download Python installer'

      exit_code = execution_utils.Exec(
          ['tar', '-xf', MACOS_PYTHON], no_exit=True)
      if exit_code != 0:
        return 'Failed to extract Python installer'

      exit_code = execution_utils.Exec([
          'sudo', 'installer', '-target', '/', '-pkg',
          './python-3.12.8-macos11.pkg'
      ], no_exit=True)
      if exit_code != 0:
        return 'Installer failed.'

  return None


def PromptAndInstallPythonOnMac():
  """Optionally install Python on Mac machines."""
  if platforms.OperatingSystem.Current() != platforms.OperatingSystem.MACOSX:
    return

  print(
      f'\nGoogle Cloud CLI works best with Python {PYTHON_VERSION} '
      'and certain modules.\n')

  # Determine python install path
  homebrew_installed = _IsHomebrewInstalled()

  if homebrew_installed:
    python_to_use = f'{HOMEBREW_BIN}/python{PYTHON_VERSION}'
  else:
    python_to_use = f'{MACOS_PYTHON_INSTALL_PATH}bin/python3'
  already_installed = os.path.isfile(python_to_use)

  # Prompt for user permission
  if already_installed:
    prompt = _PromptPythonUpdate(python_to_use)
  else:
    prompt = _PromptPythonInstall()
  if not console_io.PromptContinue(prompt_string=prompt, default=True):
    return

  # Install python if needed
  if not already_installed:
    install_errors = (_BrewInstallPython()
                      if homebrew_installed else _MacInstallPython())
  else:
    install_errors = None

  # Update python dependencies
  if not install_errors:
    os.environ['CLOUDSDK_PYTHON'] = python_to_use
    print('Setting up virtual environment')
    UpdatePythonDependencies(python_to_use)
  else:
    print(f'Failed to install Python. Error: {install_errors}')


def CheckXcodeCommandLineToolsInstalled() -> bool:
  """Checks if Xcode Command Line Tools is installed."""
  exit_code = execution_utils.Exec(['xcode-select', '-p'], no_exit=True)
  return exit_code == 0


def PromptAndInstallXcodeCommandLineTools():
  """Optionally install Xcode Command Line Tools on Mac machines."""
  if platforms.OperatingSystem.Current() != platforms.OperatingSystem.MACOSX:
    return

  if CheckXcodeCommandLineToolsInstalled():
    print('Xcode Command Line Tools is already installed.')
    return

  prompt = (
      'Xcode Command Line Tools is required to install Python. Continue to'
      ' install (Y/n)?'
  )
  setup_xcode = console_io.PromptContinue(prompt_string=prompt, default=True)

  if setup_xcode:
    print('Installing Xcode Command Line Tools...')
    xcode_command = ['xcode-select', '--install']
    exit_code = execution_utils.Exec(xcode_command, no_exit=True)
    if exit_code != 0:
      print('Failed to install Xcode Command Line Tools. '
            'Please run `xcode-select --install` manually to install '
            'Xcode Command Line Tools.')
    else:
      print('Xcode Command Line Tools is installed.')