• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 30264819148

27 Jul 2026 12:10PM UTC coverage: 72.612% (+0.05%) from 72.561%
30264819148

Pull #2212

github

web-flow
Merge ee8913c7a into dd45e9162
Pull Request #2212: #2190: IDEasy destroys python installation

4990 of 7592 branches covered (65.73%)

Branch coverage included in aggregate %.

12845 of 16970 relevant lines covered (75.69%)

3.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.48
cli/src/main/java/com/devonfw/tools/ide/tool/python/Python.java
1
package com.devonfw.tools.ide.tool.python;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.nio.file.StandardCopyOption;
6
import java.util.Set;
7

8
import org.slf4j.Logger;
9
import org.slf4j.LoggerFactory;
10

11
import com.devonfw.tools.ide.cli.CliException;
12
import com.devonfw.tools.ide.common.Tag;
13
import com.devonfw.tools.ide.context.IdeContext;
14
import com.devonfw.tools.ide.io.FileAccess;
15
import com.devonfw.tools.ide.process.EnvironmentContext;
16
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
17
import com.devonfw.tools.ide.tool.ToolCommandlet;
18
import com.devonfw.tools.ide.tool.ToolInstallRequest;
19
import com.devonfw.tools.ide.tool.ToolInstallation;
20
import com.devonfw.tools.ide.tool.repository.ToolRepository;
21
import com.devonfw.tools.ide.tool.uv.Uv;
22
import com.devonfw.tools.ide.version.VersionIdentifier;
23

24
/**
25
 * {@link ToolCommandlet} for <a href="https://www.python.org/">python</a>.
26
 */
27
public class Python extends LocalToolCommandlet {
28

29
  private static final Logger LOG = LoggerFactory.getLogger(Python.class);
4✔
30

31
  private final VersionIdentifier PYTHON_MIN_VERSION = VersionIdentifier.of("3.8.2");
4✔
32

33
  /**
34
   * The constructor.
35
   *
36
   * @param context the {@link IdeContext}.
37
   */
38
  public Python(IdeContext context) {
39

40
    super(context, "python", Set.of(Tag.PYTHON));
6✔
41
  }
1✔
42

43
  @Override
44
  protected void performToolInstallation(ToolInstallRequest request, Path installationPath) {
45

46
    VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion();
4✔
47
    if (resolvedVersion.compareVersion(PYTHON_MIN_VERSION).isLess()) {
6!
48
      throw new CliException("Python version must be at least " + this.PYTHON_MIN_VERSION);
×
49
    }
50

51
    FileAccess fileAccess = this.context.getFileAccess();
4✔
52
    if (Files.exists(installationPath)) {
5!
53
      fileAccess.backup(installationPath);
×
54
    }
55
    Path softwarePath = installationPath.getParent();
3✔
56
    Uv uv = this.context.getCommandletManager().getCommandlet(Uv.class);
7✔
57

58
    uv.installPython(softwarePath, resolvedVersion, request.getProcessContext());
6✔
59
    renameVenvFolderToPython(fileAccess, softwarePath, installationPath);
5✔
60
    this.context.writeVersionFile(resolvedVersion, installationPath);
5✔
61
    createWindowsSymlinkBinFolder(fileAccess, installationPath);
4✔
62
    LOG.debug("Installed {} in version {} at {}", this.tool, resolvedVersion, installationPath);
18✔
63
  }
1✔
64

65
  @Override
66
  public void setEnvironment(EnvironmentContext environmentContext, ToolInstallation toolInstallation, boolean additionalInstallation) {
67

68
    super.setEnvironment(environmentContext, toolInstallation, additionalInstallation);
5✔
69
    environmentContext.withEnvVar("VIRTUAL_ENV", toolInstallation.rootDir().toString());
7✔
70
  }
1✔
71

72
  @Override
73
  protected boolean isIgnoreSoftwareRepo() {
74

75
    return true;
2✔
76
  }
77

78
  @Override
79
  public ToolRepository getToolRepository() {
80

81
    return this.context.getPythonRepository();
4✔
82
  }
83

84
  @Override
85
  protected VersionIdentifier getInstalledVersion(Path toolPath) {
86
    VersionIdentifier installedVersion = super.getInstalledVersion(toolPath);
4✔
87
    if (installedVersion != null) {
2✔
88
      return installedVersion;
2✔
89
    }
90
    if ((toolPath == null) || !Files.isDirectory(toolPath)) {
7✔
91
      return null;
2✔
92
    }
93

94
    if (this.context.isBatchMode()) {
4✔
95
      throw new CliException(
7✔
96
          "Python installation exists at " + toolPath + " but " + IdeContext.FILE_SOFTWARE_VERSION
97
              + " is missing. Please restore the file or rerun interactively to enter the installed version.");
98
    }
99

100
    String version = this.context.askForInput(
7✔
101
        "Python installation exists at " + toolPath + " but " + IdeContext.FILE_SOFTWARE_VERSION
102
            + " is missing. \nEnter the installed Python version: ");
103
    VersionIdentifier recoveredVersion = VersionIdentifier.of(version);
3✔
104
    this.context.writeVersionFile(recoveredVersion, toolPath);
5✔
105
    LOG.warn("Recovered missing version file for Python at {} using version {}", toolPath, recoveredVersion);
5✔
106
    return recoveredVersion;
2✔
107
  }
108

109
  /**
110
   * Creates a symlink from the "Scripts" folder to the "bin" folder on Windows systems. This is necessary for compatibility with tools that expect a "bin"
111
   * directory.
112
   *
113
   * @param fileAccess the {@link FileAccess} utility for file operations.
114
   * @param installationPath the path where Python is installed.
115
   */
116
  private void createWindowsSymlinkBinFolder(FileAccess fileAccess, Path installationPath) {
117

118
    if (!this.context.getSystemInfo().isWindows()) {
5!
119
      return;
1✔
120
    }
121
    Path scriptsPath = installationPath.resolve("Scripts");
×
122
    Path binPath = installationPath.resolve("bin");
×
123
    fileAccess.symlink(scriptsPath, binPath);
×
124
  }
×
125

126
  /**
127
   * Renames the ".venv" folder into the installation path (Python).
128
   *
129
   * @param fileAccess the {@link FileAccess} utility for file operations.
130
   * @param softwarePath the path where the software is installed.
131
   * @param installationPath the target path where the ".venv" folder should be moved.
132
   */
133
  private void renameVenvFolderToPython(FileAccess fileAccess, Path softwarePath, Path installationPath) {
134

135
    Path venvPath = softwarePath.resolve(".venv");
4✔
136
    fileAccess.move(venvPath, installationPath, StandardCopyOption.REPLACE_EXISTING);
10✔
137
  }
1✔
138

139
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc