blob: 0ba5e7924f3f93af25dce57f20b7676dccf96ef1 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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.
*/
package com.jetbrains.python.buildout;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.ParametersList;
import com.intellij.execution.configurations.ParamsGroup;
import com.intellij.facet.Facet;
import com.intellij.facet.FacetManager;
import com.intellij.facet.FacetType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.jetbrains.python.PythonHelpersLocator;
import com.jetbrains.python.buildout.config.BuildoutCfgLanguage;
import com.jetbrains.python.buildout.config.psi.impl.BuildoutCfgFile;
import com.jetbrains.python.facet.FacetLibraryConfigurator;
import com.jetbrains.python.facet.LibraryContributingFacet;
import com.jetbrains.python.facet.PythonPathContributingFacet;
import com.jetbrains.python.run.PythonCommandLineState;
import com.jetbrains.python.sdk.PythonEnvUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Facet for buildout support.
* Knows which script in bin/ contains paths we want to add.
* User: dcheryasov
* Date: Jul 25, 2010 3:23:50 PM
*/
public class BuildoutFacet extends Facet<BuildoutFacetConfiguration> implements PythonPathContributingFacet, LibraryContributingFacet {
private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.buildout.BuildoutFacet");
@NonNls public static final String BUILDOUT_CFG = "buildout.cfg";
@NonNls public static final String SCRIPT_SUFFIX = "-script";
private static final String BUILDOUT_LIB_NAME = "Buildout Eggs";
public BuildoutFacet(@NotNull final FacetType facetType,
@NotNull final Module module,
@NotNull final String name,
@NotNull final BuildoutFacetConfiguration configuration, Facet underlyingFacet) {
super(facetType, module, name, configuration, underlyingFacet);
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
@Override
public void contentsChanged(@NotNull VirtualFileEvent event) {
if (Comparing.equal(event.getFile(), getScript())) {
updatePaths();
attachLibrary(module);
}
}
}, this);
}
@Nullable
public static VirtualFile getRunner(VirtualFile baseDir) {
if (baseDir == null) return null;
final VirtualFile cfg = baseDir.findChild(BUILDOUT_CFG);
if (cfg != null && !cfg.isDirectory()) {
VirtualFile eggs = baseDir.findChild("eggs");
if (eggs != null && eggs.isDirectory()) {
VirtualFile bin = baseDir.findChild("bin");
if (bin != null && bin.isDirectory()) {
if (ApplicationManager.getApplication().isDispatchThread() || !ApplicationManager.getApplication().isReadAccessAllowed()) {
bin.refresh(false, false);
}
final String exe;
if (SystemInfo.isWindows || SystemInfo.isOS2) {
exe = "buildout.exe";
}
else {
exe = "buildout";
}
VirtualFile runner = bin.findChild(exe);
if (runner != null && !runner.isDirectory()) {
return runner;
}
}
}
}
return null;
}
/**
* Generates a <code>sys.path[0:0] = [...]</code> with paths that buildout script wants.
*
* @param additionalPythonPath
* @param module to get a buildout facet from
* @return the statement, or null if there's no buildout facet.
*/
@Nullable
public String getPathPrependStatement(List<String> additionalPythonPath) {
StringBuilder sb = new StringBuilder("sys.path[0:0]=[");
for (String s : additionalPythonPath) {
sb.append("'").append(s).append("',");
// NOTE: we assume that quotes and spaces are escaped in paths back in the buildout script we extracted them from.
}
sb.append("]");
return sb.toString();
}
@Override
public void initFacet() {
updateLibrary();
}
@Override
public void updateLibrary() {
updatePaths();
attachLibrary(getModule());
}
@Override
public void removeLibrary() {
detachLibrary(getModule());
}
public void updatePaths() {
BuildoutFacetConfiguration config = getConfiguration();
final VirtualFile script = getScript();
if (script != null) {
config.setPaths(extractBuildoutPaths(script));
}
}
@Nullable
public VirtualFile getScript() {
return LocalFileSystem.getInstance().findFileByPath(getConfiguration().getScriptName());
}
@Nullable
public static List<String> extractBuildoutPaths(@NotNull VirtualFile script) {
try {
List<String> paths = extractFromScript(script);
if (paths == null) {
VirtualFile root = script.getParent().getParent();
String partName = FileUtil.getNameWithoutExtension(script.getName());
if (SystemInfo.isWindows && partName.endsWith(SCRIPT_SUFFIX)) {
partName = partName.substring(0, partName.length() - SCRIPT_SUFFIX.length());
}
VirtualFile sitePy = root.findFileByRelativePath("parts/" + partName + "/site.py");
if (sitePy != null) {
paths = extractFromSitePy(sitePy);
}
}
return paths;
}
catch (IOException e) {
LOG.info(e);
return null;
}
}
/**
* Extracts paths from given script, assuming sys.path[0:0] assignment.
*
* @param script
* @return extracted paths, or null if extraction fails.
*/
@Nullable
public static List<String> extractFromScript(@NotNull VirtualFile script) throws IOException {
String text = VfsUtil.loadText(script);
Pattern pat = Pattern.compile("(?:^\\s*(['\"])(.*)(\\1),\\s*$)|(\\])", Pattern.MULTILINE);
final String bait_string = "sys.path[0:0]";
int pos = text.indexOf(bait_string);
List<String> ret = null;
if (pos >= 0) {
pos += bait_string.length();
Matcher scanner = pat.matcher(text);
while (scanner.find(pos)) {
String value = scanner.group(2);
if (value != null) {
if (ret == null) {
ret = new ArrayList<String>();
}
ret.add(value);
pos = scanner.end();
}
else {
break;
} // we've matched the ']', it's group(4)
}
}
return ret;
}
/**
* Extracts paths from site.py generated by buildout 1.5+
*
* @param vFile path to site.py
* @return extracted paths
*/
public static List<String> extractFromSitePy(VirtualFile vFile) throws IOException {
List<String> result = new ArrayList<String>();
String text = VfsUtil.loadText(vFile);
String[] lines = LineTokenizer.tokenize(text, false);
int index = 0;
while (index < lines.length && !lines[index].startsWith("def addsitepackages(")) {
index++;
}
while (index < lines.length && !lines[index].trim().startsWith("buildout_paths = [")) {
index++;
}
index++;
while (index < lines.length && !lines[index].trim().equals("]")) {
String line = lines[index].trim();
if (line.endsWith(",")) {
line = line.substring(0, line.length() - 1);
}
if (line.startsWith("'") && line.endsWith("'")) {
result.add(StringUtil.unescapeStringCharacters(line.substring(1, line.length() - 1)));
}
index++;
}
return result;
}
@Override
public List<String> getAdditionalPythonPath() {
BuildoutFacetConfiguration cfg = getConfiguration();
return cfg.getPaths();
}
@Override
public boolean acceptRootAsTopLevelPackage() {
return false;
}
@Nullable
public static BuildoutFacet getInstance(Module module) {
return FacetManager.getInstance(module).getFacetByType(BuildoutFacetType.ID);
}
public void patchCommandLineForBuildout(GeneralCommandLine commandLine) {
Map<String, String> env = commandLine.getEnvironment();
ParametersList params = commandLine.getParametersList();
// alter execution script
ParamsGroup script_params = params.getParamsGroup(PythonCommandLineState.GROUP_SCRIPT);
assert script_params != null;
if (script_params.getParameters().size() > 0) {
String normal_script = script_params.getParameters().get(0); // expect DjangoUtil.MANAGE_FILE
String engulfer_path = PythonHelpersLocator.getHelperPath("pycharm/buildout_engulfer.py");
env.put("PYCHARM_ENGULF_SCRIPT", getConfiguration().getScriptName());
script_params.getParametersList().replaceOrPrepend(normal_script, engulfer_path);
}
// add pycharm helpers to pythonpath so that fixGetpass is importable
PythonEnvUtil.addToPythonPath(env, PythonHelpersLocator.getHelpersRoot().getAbsolutePath());
/*
// set prependable paths
List<String> paths = facet.getAdditionalPythonPath();
if (paths != null) {
path_value = PyUtil.joinWith(File.pathSeparator, paths);
env.put("PYCHARM_PREPEND_SYSPATH", path_value);
}
*/
}
@Nullable
public File getConfigFile() {
final String scriptName = getConfiguration().getScriptName();
if (!StringUtil.isEmpty(scriptName)) {
return new File(new File(scriptName).getParentFile().getParentFile(), BUILDOUT_CFG);
}
return null;
}
@Nullable
public BuildoutCfgFile getConfigPsiFile() {
File cfg = getConfigFile();
if (cfg != null && cfg.exists()) {
try {
// this method is called before the project initialization is complete, so it has to use createFileFromText() instead
// of PsiManager.findFile()
String text = FileUtil.loadFile(cfg);
final PsiFile configFile = PsiFileFactory
.getInstance(getModule().getProject()).createFileFromText("buildout.cfg",
BuildoutCfgLanguage.INSTANCE, text);
if (configFile != null && configFile instanceof BuildoutCfgFile) {
return (BuildoutCfgFile)configFile;
}
}
catch (Exception ignored) {
}
}
return null;
}
public static List<File> getScripts(@Nullable BuildoutFacet buildoutFacet, final VirtualFile baseDir) {
File rootPath = null;
if (buildoutFacet != null) {
final File configIOFile = buildoutFacet.getConfigFile();
if (configIOFile != null) {
rootPath = configIOFile.getParentFile();
}
}
if (rootPath == null || !rootPath.exists()) {
if (baseDir != null) {
rootPath = new File(baseDir.getPath());
}
}
if (rootPath != null) {
final File[] scripts = new File(rootPath, "bin").listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if (SystemInfo.isWindows) {
return name.endsWith("-script.py");
}
String ext = FileUtilRt.getExtension(name);
return ext.length() == 0 || FileUtil.namesEqual(ext, "py");
}
});
if (scripts != null) {
return Arrays.asList(scripts);
}
}
return Collections.emptyList();
}
@Nullable
public static File findScript(@Nullable BuildoutFacet buildoutFacet, String name, final VirtualFile baseDir) {
String scriptName = SystemInfo.isWindows ? name + SCRIPT_SUFFIX : name;
final List<File> scripts = getScripts(buildoutFacet, baseDir);
for (File script : scripts) {
if (FileUtil.getNameWithoutExtension(script.getName()).equals(scriptName)) {
return script;
}
}
return null;
}
public static void attachLibrary(final Module module) {
final BuildoutFacet facet = getInstance(module);
if (facet == null) {
return;
}
final List<String> paths = facet.getConfiguration().getPaths();
FacetLibraryConfigurator.attachPythonLibrary(module, null, BUILDOUT_LIB_NAME, paths);
}
public static void detachLibrary(final Module module) {
FacetLibraryConfigurator.detachPythonLibrary(module, BUILDOUT_LIB_NAME);
}
}