package com.siberika.idea.pascal.sdk; import com.google.common.collect.ImmutableMap; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.projectRoots.AdditionalDataConfigurable; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkModel; import com.intellij.openapi.projectRoots.SdkModificator; import com.intellij.openapi.projectRoots.SdkType; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.siberika.idea.pascal.PascalException; import com.siberika.idea.pascal.PascalIcons; import com.siberika.idea.pascal.jps.compiler.DelphiBackendCompiler; import com.siberika.idea.pascal.jps.model.JpsPascalModelSerializerExtension; import com.siberika.idea.pascal.jps.sdk.PascalCompilerFamily; import com.siberika.idea.pascal.jps.sdk.PascalSdkData; import com.siberika.idea.pascal.jps.sdk.PascalSdkUtil; import com.siberika.idea.pascal.util.SysUtils; import org.apache.commons.lang.text.StrBuilder; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.io.File; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Author: George Bakhtadze * Date: 10/01/2013 */ public class DelphiSdkType extends BasePascalSdkType { private static final Logger LOG = Logger.getInstance(DelphiSdkType.class.getName()); private static final String[] LIBRARY_DIRS = {"debug"}; private static final Pattern DELPHI_VERSION_PATTERN = Pattern.compile("[\\w\\s]+[vV]ersion\\s(\\d+\\.\\d+)"); private static final String DELPHI_STARTER_VERSION_PATTERN = DelphiBackendCompiler.DELPHI_STARTER_RESPONSE; private static final Pattern RTLPKG_PATTERN = Pattern.compile("RTL(\\d{3,4}).BPL"); private static final String[] EMPTY_STRINGS = new String[0]; private static final Map<Integer, String> rtlPkgVersionMap = new ImmutableMap.Builder<Integer, String>(). put(240, "31"). put(250, "32"). put(260, "33"). put(270, "34"). put(280, "35"). put(290, "36") .build(); @NotNull public static DelphiSdkType getInstance() { return SdkType.findInstance(DelphiSdkType.class); } public DelphiSdkType() { super(JpsPascalModelSerializerExtension.DELPHI_SDK_TYPE_ID, PascalCompilerFamily.DELPHI); loadResources("delphi"); } @Nullable @Override public String suggestHomePath() { List<String> dirs = Arrays.asList("", "program files"); for (File drive : File.listRoots()) { if (drive.isDirectory()) { for (String dir : dirs) { String s = checkDir(new File(drive, dir)); if (s != null) return s; } } } return null; } private String checkDir(@NotNull File file) { List<String> ides = Arrays.asList("delphi", "rad studio"); for (String ide : ides) { File f = new File(file, ide); LOG.info("=== checking directory " + f.getAbsolutePath()); if (f.isDirectory()) { return f.getAbsolutePath(); } } return null; } @NotNull @Override public Icon getIcon() { return PascalIcons.GENERAL; } @NotNull @Override public Icon getIconForAddAction() { return getIcon(); } @Override public boolean isValidSdkHome(@NotNull final String path) { LOG.info("Checking SDK path: " + path); final File dcc32Exe = PascalSdkUtil.getDCC32Executable(path); return dcc32Exe.isFile() && dcc32Exe.canExecute(); } @NotNull public String suggestSdkName(@Nullable final String currentSdkName, @NotNull final String sdkHome) { String version = getVersionString(sdkHome); final String prefix = String.format("Delphi %sv. ", isStarter(getVersionLines(sdkHome)) ? "(Starter) " : ""); if (version != null) { return prefix + version + " | " + getTargetString(sdkHome); } else { return prefix + "?? at " + sdkHome; } } private String getVersion(String[] lines) { for (String line : lines) { Matcher m = DELPHI_VERSION_PATTERN.matcher(line); if (m.matches()) { return m.group(1); } } return null; } private boolean isStarter(String[] lines) { LOG.info("Checking for starter edition"); for (String line : lines) { LOG.info("=== Line: " + line); if ((line != null) && line.startsWith(DELPHI_STARTER_VERSION_PATTERN)) { return true; } } return false; } @NotNull private String[] getVersionLines(String sdkHome) { try { String out = SysUtils.runAndGetStdOut(sdkHome, PascalSdkUtil.getDCC32Executable(sdkHome).getAbsolutePath(), PascalSdkUtil.DELPHI_PARAMS_VERSION_GET); return out != null ? out.split("\n", 3) : EMPTY_STRINGS; } catch (PascalException e) { LOG.info("Error: " + e.getMessage(), e); return EMPTY_STRINGS; } } @Nullable @Override public String getVersionString(String sdkHome) { LOG.info("Getting version for SDK path: " + sdkHome); String[] lines = getVersionLines(sdkHome); if (isStarter(lines)) { return getVersionByRTL(sdkHome); } else { return getVersion(lines); } } private String getVersionByRTL(String sdkHome) { File binDir = new File(sdkHome, "bin"); File[] rtl = binDir.listFiles(); Integer version = null; if (rtl != null) { for (File file : rtl) { Matcher m = RTLPKG_PATTERN.matcher(file.getName().toUpperCase()); if (m.matches()) { int v = Integer.parseInt(m.group(1)); if ((null == version) || (v > version)) { version = v; } } } } return rtlPkgVersionMap.get(version); } @NotNull private static String getTargetString(String sdkHome) { LOG.info("Getting target for SDK path: " + sdkHome); return "Win32"; } @NotNull @NonNls @Override public String getPresentableName() { return "Delphi SDK"; } @Override public void setupSdkPaths(@NotNull final Sdk sdk) { configureSdkPaths(sdk); configureOptions(sdk, getAdditionalData(sdk), ""); } @Override protected void configureOptions(@NotNull Sdk sdk, PascalSdkData data, String target) { super.configureOptions(sdk, data, target); StrBuilder sb = new StrBuilder(); sb.append("-dWINDOWS "); sb.append("-dWIN32 "); data.setValue(PascalSdkData.Keys.COMPILER_OPTIONS.getKey(), sb.toString()); if (isStarter(getVersionLines(sdk.getHomePath()))) { data.setValue(PascalSdkData.Keys.DELPHI_IS_STARTER.getKey(), PascalSdkData.SDK_DATA_TRUE); } } private static void configureSdkPaths(@NotNull final Sdk sdk) { LOG.info("Setting up SDK paths for SDK at " + sdk.getHomePath()); final SdkModificator[] sdkModificatorHolder = new SdkModificator[]{null}; final SdkModificator sdkModificator = sdk.getSdkModificator(); for (String dir : LIBRARY_DIRS) { VirtualFile vdir = getLibrary(sdk, dir); if (vdir != null) { sdkModificator.addRoot(vdir, OrderRootType.CLASSES); } } sdkModificatorHolder[0] = sdkModificator; sdkModificatorHolder[0].commitChanges(); } private static VirtualFile getLibrary(Sdk sdk, String name) { String target = "win32"; File rtlDir = new File(sdk.getHomePath() + File.separatorChar + "lib" + File.separatorChar + target + File.separatorChar + name); if (!rtlDir.exists()) { rtlDir = new File(sdk.getHomePath() + File.separatorChar + "lib" + File.separatorChar + name); } return LocalFileSystem.getInstance().findFileByIoFile(rtlDir); } @Override public boolean isRootTypeApplicable(@NotNull OrderRootType type) { return type.equals(OrderRootType.SOURCES) || type.equals(OrderRootType.CLASSES); } @Nullable @Override public AdditionalDataConfigurable createAdditionalDataConfigurable(@NotNull final SdkModel sdkModel, @NotNull final SdkModificator sdkModificator) { return new PascalSdkConfigUI(); } }