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

Camelcade / Perl5-IDEA / #525521559

30 May 2025 06:24AM UTC coverage: 82.248% (-0.03%) from 82.275%
#525521559

push

github

hurricup
Suppressed SameParameterValue warning

30866 of 37528 relevant lines covered (82.25%)

0.82 hits per line

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

69.47
/plugin/core/src/main/java/com/perl5/lang/perl/idea/sdk/PerlSdkType.java
1
/*
2
 * Copyright 2015-2025 Alexandr Evstigneev
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.perl5.lang.perl.idea.sdk;
18

19
import com.intellij.notification.Notification;
20
import com.intellij.notification.NotificationType;
21
import com.intellij.notification.Notifications;
22
import com.intellij.openapi.application.ApplicationManager;
23
import com.intellij.openapi.application.WriteAction;
24
import com.intellij.openapi.diagnostic.Logger;
25
import com.intellij.openapi.project.Project;
26
import com.intellij.openapi.projectRoots.*;
27
import com.intellij.openapi.projectRoots.impl.PerlSdkTable;
28
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
29
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
30
import com.intellij.openapi.roots.OrderRootType;
31
import com.intellij.openapi.ui.Messages;
32
import com.intellij.openapi.util.SystemInfo;
33
import com.intellij.openapi.util.text.StringUtil;
34
import com.intellij.openapi.vfs.VfsUtil;
35
import com.intellij.openapi.vfs.VirtualFile;
36
import com.intellij.util.ObjectUtils;
37
import com.intellij.util.containers.ContainerUtil;
38
import com.perl5.PerlBundle;
39
import com.perl5.PerlIcons;
40
import com.perl5.lang.perl.idea.project.PerlProjectManager;
41
import com.perl5.lang.perl.idea.sdk.host.PerlHostData;
42
import com.perl5.lang.perl.idea.sdk.host.PerlHostFileTransfer;
43
import com.perl5.lang.perl.idea.sdk.implementation.PerlImplementationData;
44
import com.perl5.lang.perl.idea.sdk.implementation.PerlImplementationHandler;
45
import com.perl5.lang.perl.idea.sdk.versionManager.PerlVersionManagerData;
46
import com.perl5.lang.perl.util.PerlRunUtil;
47
import org.jdom.Element;
48
import org.jetbrains.annotations.Contract;
49
import org.jetbrains.annotations.NotNull;
50
import org.jetbrains.annotations.Nullable;
51

52
import javax.swing.*;
53
import java.io.File;
54
import java.io.IOException;
55
import java.util.ArrayList;
56
import java.util.List;
57
import java.util.Objects;
58
import java.util.function.Consumer;
59

60

61
public class PerlSdkType extends SdkType {
62
  private static final Logger LOG = Logger.getInstance(PerlSdkType.class);
1✔
63
  public static final String PERL_SDK_TYPE_ID = "Perl5 Interpreter";
64
  public static final PerlSdkType INSTANCE = new PerlSdkType();
1✔
65

66

67
  private PerlSdkType() {
68
    super(PERL_SDK_TYPE_ID);
1✔
69
  }
1✔
70

71
  @Override
72
  public void saveAdditionalData(@NotNull SdkAdditionalData sdkAdditionalData, @NotNull Element element) {
73
    ((SaveAwareSdkAdditionalData)sdkAdditionalData).save(element);
1✔
74
  }
1✔
75

76
  @Override
77
  public @NotNull SdkAdditionalData loadAdditionalData(@NotNull Element additional) {
78
    return PerlSdkAdditionalData.load(additional);
1✔
79
  }
80

81
  @Override
82
  public void setupSdkPaths(@NotNull Sdk sdk) {
83
    if (ApplicationManager.getApplication().isDispatchThread() && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
1✔
84
      throw new RuntimeException("Do not call from EDT, refreshes FS");
×
85
    }
86
    String oldText = PerlRunUtil.setProgressText(PerlBundle.message("perl.progress.refreshing.inc", sdk.getName()));
1✔
87
    LOG.info("Refreshing @INC for " + sdk);
1✔
88
    PerlHostData<?, ?> hostData = PerlHostData.notNullFrom(sdk);
1✔
89
    List<String> pathsToRefresh = new ArrayList<>();
1✔
90
    // syncing data if necessary
91
    List<String> incPaths = computeIncPaths(sdk);
1✔
92
    List<Exception> exceptions = new ArrayList<>();
1✔
93

94
    try (PerlHostFileTransfer<?> fileTransfer = hostData.getFileTransfer()) {
1✔
95
      Consumer<File> downloader = it -> syncAndCollectException(fileTransfer, it, pathsToRefresh, exceptions, false);
1✔
96
      Consumer<File> binStubber = it -> syncAndCollectException(fileTransfer, it, pathsToRefresh, exceptions, true);
1✔
97
      for (String hostPath : incPaths) {
1✔
98
        downloader.accept(new File(hostPath));
1✔
99
        binStubber.accept(PerlRunUtil.findLibsBin(new File(hostPath)));
1✔
100
      }
1✔
101
      // additional bin dirs from version manager
102
      PerlVersionManagerData.notNullFrom(sdk).getBinDirsPath().forEach(binStubber);
1✔
103

104
      // sdk home path
105
      File interpreterPath = new File(Objects.requireNonNull(PerlProjectManager.getInterpreterPath(sdk)));
1✔
106
      binStubber.accept(interpreterPath.getParentFile());
1✔
107

108
      List<VirtualFile> filesToRefresh = pathsToRefresh.stream()
1✔
109
        .map(it -> VfsUtil.findFileByIoFile(new File(it), true))
1✔
110
        .filter(Objects::nonNull)
1✔
111
        .toList();
1✔
112

113
      if (!filesToRefresh.isEmpty()) {
1✔
114
        PerlRunUtil.setProgressText(PerlBundle.message("perl.progress.refreshing.filesystem"));
1✔
115
        VfsUtil.markDirtyAndRefresh(false, true, true, filesToRefresh.toArray(VirtualFile.EMPTY_ARRAY));
1✔
116
      }
117

118
      try {
119
        fileTransfer.syncHelpers();
1✔
120
      }
121
      catch (IOException e) {
×
122
        exceptions.add(e);
×
123
      }
1✔
124

125
      if (!exceptions.isEmpty()) {
1✔
126
        int copiedFiles = filesToRefresh.size();
×
127
        int errorsNumber = exceptions.size();
×
128
        Notifications.Bus.notify(new Notification(
×
129
          PerlBundle.message("perl.sync.notification.group"),
×
130
          PerlBundle.message("perl.sync.notification.title"),
×
131
          PerlBundle.message("perl.sync.notification.body",
×
132
                             copiedFiles, copiedFiles + errorsNumber, errorsNumber,
×
133
                             StringUtil.pluralize(PerlBundle.message("perl.sync.notification.pluralize"), errorsNumber)),
×
134
          NotificationType.ERROR));
135
        exceptions.forEach(LOG::warn);
×
136
      }
137
    }
138
    catch (IOException e) {
×
139
      LOG.warn("Error closing transfer for " + sdk, e);
×
140
    }
1✔
141

142
    // updating sdk
143
    SdkModificator sdkModificator = sdk.getSdkModificator();
1✔
144
    sdkModificator.removeAllRoots();
1✔
145
    for (String hostPath : incPaths) {
1✔
146
      String localPath = hostData.getLocalPath(hostPath);
1✔
147
      if (localPath == null) {
1✔
148
        continue;
×
149
      }
150
      File libDir = new File(localPath);
1✔
151

152
      if (libDir.exists() && libDir.isDirectory()) {
1✔
153
        VirtualFile virtualDir = VfsUtil.findFileByIoFile(libDir, true);
1✔
154
        if (virtualDir != null) {
1✔
155
          sdkModificator.addRoot(virtualDir, OrderRootType.CLASSES);
1✔
156
        }
157
      }
158
    }
1✔
159

160
    ApplicationManager.getApplication().invokeAndWait(() -> WriteAction.run(sdkModificator::commitChanges));
1✔
161
    PerlRunUtil.setProgressText(oldText);
1✔
162
  }
1✔
163

164
  /**
165
   * Copying {@code fileToCopy} using the {@code fileTransfer} and collecting local path of copied file to the {@code pathsToRefresh}. In
166
   * case exception been thrown by the {@code fileTransfer}, it's collected to the {@code exceptionsThrown}
167
   *
168
   * @param if set, only empty stubs with same names going to be created, instead of actual copying the file.
169
   */
170
  private static void syncAndCollectException(@NotNull PerlHostFileTransfer<?> fileTransfer,
171
                                              @Nullable File fileToCopy,
172
                                              @NotNull List<String> pathsToRefresh,
173
                                              @NotNull List<Exception> exceptionsThrown,
174
                                              boolean stubOnly) {
175
    if (fileToCopy == null) {
1✔
176
      return;
×
177
    }
178
    try {
179
      if (stubOnly) {
1✔
180
        pathsToRefresh.add(fileTransfer.stubFiles(fileToCopy).getPath());
1✔
181
      }
182
      else {
183
        pathsToRefresh.add(fileTransfer.syncFile(fileToCopy).getPath());
1✔
184
      }
185
    }
186
    catch (IOException e) {
×
187
      exceptionsThrown.add(e);
×
188
    }
1✔
189
  }
1✔
190

191

192
  @SuppressWarnings("deprecation")
193
  @Override
194
  public @Nullable String suggestHomePath() {
195
    throw new RuntimeException("unsupported");
×
196
  }
197

198
  @Override
199
  public @Nullable AdditionalDataConfigurable createAdditionalDataConfigurable(@NotNull SdkModel sdkModel,
200
                                                                               @NotNull SdkModificator sdkModificator) {
201
    return null;
×
202
  }
203

204
  @Override
205
  public @NotNull String getPresentableName() {
206
    return PerlBundle.message("perl.config.interpreter.title");
×
207
  }
208

209
  @Override
210
  public @NotNull String suggestSdkName(String currentSdkName, @NotNull String sdkHome) {
211
    throw new RuntimeException("Should not be invoked");
×
212
  }
213

214
  @Override
215
  public @Nullable String getVersionString(@NotNull Sdk sdk) {
216
    return ObjectUtils.doIfNotNull(
×
217
      getPerlVersionDescriptor(PerlProjectManager.getInterpreterPath(sdk),
×
218
                               PerlHostData.notNullFrom(sdk),
×
219
                               PerlVersionManagerData.notNullFrom(sdk)),
×
220
      VersionDescriptor::getVersionString);
221
  }
222

223
  private static @NotNull List<String> computeIncPaths(@NotNull Sdk sdk) {
224
    return ContainerUtil.filter(PerlRunUtil.getOutputFromPerl(sdk, PerlRunUtil.PERL_LE, "print for @INC"), it -> !".".equals(it));
1✔
225
  }
226

227
  @Override
228
  public boolean isValidSdkHome(@NotNull String sdkHome) {
229
    throw new RuntimeException("Unsupported");
×
230
  }
231

232
  @Override
233
  public @NotNull String adjustSelectedSdkHome(@NotNull String homePath) {
234
    File file = new File(homePath);
×
235
    if (file.isDirectory()) {
×
236
      return homePath;
×
237
    }
238
    File parentFile = file.getParentFile();
×
239
    return parentFile != null && parentFile.isDirectory() ? parentFile.getPath() : homePath;
×
240
  }
241

242
  public @NotNull String getPerlExecutableName() {
243
    return SystemInfo.isWindows ? "perl.exe" : "perl";
1✔
244
  }
245

246
  @Override
247
  public Icon getIcon() {
248
    return PerlIcons.PERL_LANGUAGE_ICON;
1✔
249
  }
250

251
  private static @NotNull String suggestSdkName(@Nullable VersionDescriptor descriptor,
252
                                                @NotNull PerlHostData<?, ?> hostData,
253
                                                @NotNull PerlVersionManagerData<?, ?> versionManagerData) {
254
    return StringUtil.capitalize(hostData.getShortName()) + ", " +
1✔
255
           StringUtil.capitalize(versionManagerData.getShortName()) + ": " +
1✔
256
           "Perl" + (descriptor == null ? "" : " " + descriptor.version);
1✔
257
  }
258

259
  @Contract("null, _,_->null")
260
  public static @Nullable VersionDescriptor getPerlVersionDescriptor(@Nullable String interpreterPath,
261
                                                                     @NotNull PerlHostData<?, ?> hostData,
262
                                                                     @NotNull PerlVersionManagerData<?, ?> versionManagerData) {
263
    if (StringUtil.isEmpty(interpreterPath)) {
1✔
264
      return null;
×
265
    }
266

267
    return VersionDescriptor.create(PerlRunUtil.getOutputFromProgram(
1✔
268
      hostData, versionManagerData,
269
      interpreterPath, "-MConfig", "-e", "print q{perl}.chr(10);do {print;print chr(10)} for @Config{qw/version archname/}"));
270
  }
271

272
  public static void createAndAddSdk(@NotNull String interpreterPath,
273
                                     @NotNull PerlHostData<?, ?> hostData,
274
                                     @NotNull PerlVersionManagerData<?, ?> versionManagerData,
275
                                     @Nullable Consumer<Sdk> sdkConsumer,
276
                                     @Nullable Project project) {
277
    createSdk(interpreterPath, hostData, versionManagerData, sdk -> {
1✔
278
      PerlSdkTable.getInstance().addJdk(sdk);
1✔
279
      if (sdkConsumer != null) {
1✔
280
        sdkConsumer.accept(sdk);
1✔
281
      }
282
      PerlRunUtil.refreshSdkDirs(sdk, project);
1✔
283
    });
1✔
284
  }
1✔
285

286
  /**
287
   * Creates and adds new Perl SDK
288
   *
289
   * @param interpreterPath interpreter path
290
   * @param sdkConsumer     created sdk consumer
291
   */
292
  public static void createSdk(@NotNull String interpreterPath,
293
                               @NotNull PerlHostData<?, ?> hostData,
294
                               @NotNull PerlVersionManagerData<?, ?> versionManagerData,
295
                               @NotNull Consumer<Sdk> sdkConsumer) {
296
    VersionDescriptor perlVersionDescriptor = PerlSdkType.getPerlVersionDescriptor(interpreterPath, hostData, versionManagerData);
1✔
297
    if (perlVersionDescriptor == null) {
1✔
298
      ApplicationManager.getApplication().invokeLater(
×
299
        () -> Messages.showErrorDialog(PerlBundle.message("dialog.message.failed.to.fetch.perl.version.see.logs.for.more.details"),
×
300
                                       PerlBundle.message("dialog.title.failed.to.create.interpreter")));
×
301
      return;
×
302
    }
303
    String newSdkName = SdkConfigurationUtil.createUniqueSdkName(
1✔
304
      suggestSdkName(perlVersionDescriptor, hostData, versionManagerData), PerlSdkTable.getInstance().getInterpreters());
1✔
305

306
    final ProjectJdkImpl newSdk = PerlSdkTable.getInstance().createSdk(newSdkName);
1✔
307
    var sdkModificator = newSdk.getSdkModificator();
1✔
308
    sdkModificator.setHomePath(interpreterPath);
1✔
309

310
    PerlImplementationData<?, ?> implementationData = PerlImplementationHandler.createData(
1✔
311
      interpreterPath, hostData, versionManagerData);
312

313
    if (implementationData == null) {
1✔
314
      return;
×
315
    }
316

317
    sdkModificator.setVersionString(perlVersionDescriptor.getVersionString());
1✔
318
    sdkModificator.setSdkAdditionalData(new PerlSdkAdditionalData(hostData, versionManagerData, implementationData, PerlConfig.empty()));
1✔
319
    ApplicationManager.getApplication().invokeAndWait(() -> WriteAction.run(sdkModificator::commitChanges));
1✔
320
    PerlConfig.init(newSdk);
1✔
321
    sdkConsumer.accept(newSdk);
1✔
322
  }
1✔
323

324
  public static class VersionDescriptor {
325
    private String version;
326
    private String platform;
327

328
    private VersionDescriptor() {
329
    }
330

331
    private static @Nullable VersionDescriptor create(@NotNull List<String> perlResponse) {
332
      if (perlResponse.size() != 3 || !StringUtil.equals("perl", perlResponse.get(0))) {
1✔
333
        LOG.warn("Got inappropriate response from perl: " + StringUtil.join(perlResponse, "\n"));
×
334
        return null;
×
335
      }
336

337
      VersionDescriptor result = new VersionDescriptor();
1✔
338
      result.version = perlResponse.get(1);
1✔
339
      result.platform = perlResponse.get(2);
1✔
340
      return result;
1✔
341
    }
342

343
    public @NotNull String getVersionString() {
344
      return PerlBundle.message("perl.version.string", version, platform);
1✔
345
    }
346
  }
347
}
348

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

© 2026 Coveralls, Inc