Add version detection for Bazel and more detailed error message for choosing bazel (#27)
Fixes #3.
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/Activator.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/Activator.java
index 69aa39a..a7ff2ad 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/Activator.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/Activator.java
@@ -31,6 +31,7 @@
import com.google.common.collect.ImmutableList;
import com.google.devtools.bazel.e4b.command.BazelCommand;
import com.google.devtools.bazel.e4b.command.BazelCommand.BazelInstance;
+import com.google.devtools.bazel.e4b.command.BazelNotFoundException;
/**
* The activator class controls the plug-in life cycle
@@ -122,9 +123,11 @@
/**
* Return the {@link BazelInstance} corresponding to the given <code>project</code>. It looks for
* the instance that runs for the workspace root configured for that project.
+ *
+ * @throws BazelNotFoundException
*/
public static BazelCommand.BazelInstance getBazelCommandInstance(IProject project)
- throws BackingStoreException, IOException, InterruptedException {
+ throws BackingStoreException, IOException, InterruptedException, BazelNotFoundException {
IScopeContext projectScope = new ProjectScope(project.getProject());
Preferences projectNode = projectScope.getNode(Activator.PLUGIN_ID);
File workspaceRoot =
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/builder/BazelBuilder.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/builder/BazelBuilder.java
index 2faa4c4..4c16fd4 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/builder/BazelBuilder.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/builder/BazelBuilder.java
@@ -26,6 +26,7 @@
import com.google.devtools.bazel.e4b.Activator;
import com.google.devtools.bazel.e4b.command.BazelCommand.BazelInstance;
+import com.google.devtools.bazel.e4b.command.BazelNotFoundException;
public class BazelBuilder extends IncrementalProjectBuilder {
@@ -46,6 +47,8 @@
instance.build(Activator.getTargets(project));
} catch (BackingStoreException | IOException | InterruptedException e) {
Activator.error("Failed to build " + project.getName(), e);
+ } catch (BazelNotFoundException e) {
+ Activator.error("Bazel not found: " + e.getMessage());
}
return null;
}
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/classpath/BazelClasspathContainer.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/classpath/BazelClasspathContainer.java
index ec18b89..696122a 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/classpath/BazelClasspathContainer.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/classpath/BazelClasspathContainer.java
@@ -38,6 +38,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.devtools.bazel.e4b.Activator;
import com.google.devtools.bazel.e4b.command.BazelCommand.BazelInstance;
+import com.google.devtools.bazel.e4b.command.BazelNotFoundException;
import com.google.devtools.bazel.e4b.command.IdeBuildInfo;
import com.google.devtools.bazel.e4b.command.IdeBuildInfo.Jars;
@@ -49,7 +50,8 @@
private final BazelInstance instance;
public BazelClasspathContainer(IPath path, IJavaProject project)
- throws IOException, InterruptedException, BackingStoreException, JavaModelException {
+ throws IOException, InterruptedException, BackingStoreException, JavaModelException,
+ BazelNotFoundException {
this.path = path;
this.project = project;
this.instance = Activator.getBazelCommandInstance(project.getProject());
@@ -114,6 +116,9 @@
} catch (JavaModelException | BackingStoreException | IOException | InterruptedException e) {
Activator.error("Unable to compute classpath containers entries.", e);
return new IClasspathEntry[] {};
+ } catch (BazelNotFoundException e) {
+ Activator.error("Bazel not found: " + e.getMessage());
+ return new IClasspathEntry[] {};
}
}
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/classpath/BazelClasspathContainerInitilalizer.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/classpath/BazelClasspathContainerInitilalizer.java
index 204b0dd..8c6ab96 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/classpath/BazelClasspathContainerInitilalizer.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/classpath/BazelClasspathContainerInitilalizer.java
@@ -25,6 +25,7 @@
import org.osgi.service.prefs.BackingStoreException;
import com.google.devtools.bazel.e4b.Activator;
+import com.google.devtools.bazel.e4b.command.BazelNotFoundException;
public class BazelClasspathContainerInitilalizer extends ClasspathContainerInitializer {
@@ -40,6 +41,8 @@
}
} catch (IOException | InterruptedException | BackingStoreException e) {
Activator.error("Error while creating Bazel classpath container.", e);
+ } catch (BazelNotFoundException e) {
+ Activator.error("Bazel not found: " + e.getMessage());
}
}
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/BazelCommand.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/BazelCommand.java
index ea6b3bd..89da270 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/BazelCommand.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/BazelCommand.java
@@ -14,6 +14,10 @@
package com.google.devtools.bazel.e4b.command;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.bazel.e4b.Activator;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
@@ -23,21 +27,22 @@
import java.util.List;
import java.util.Map;
import java.util.function.Function;
-
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.devtools.bazel.e4b.Activator;
-
/**
* Main utility to call bazel commands, wrapping its input and output to the message console.
*/
public class BazelCommand {
private static Joiner NEW_LINE_JOINER = Joiner.on("\n");
+ private static Pattern VERSION_PATTERN =
+ Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)([^0-9].*)?$");
+
+ // Minimum bazel version needed to work with this plugin (currently 0.4.0)
+ private static int[] MINIMUM_BAZEL_VERSION = {0, 4, 0};
// Returns the path of the resources file from this plugin.
private static File getAspectWorkspace() {
@@ -63,20 +68,77 @@
.build();
private final Map<File, BazelInstance> instances = new HashMap<>();
- private String bazel = Activator.DEFAULT_BAZEL_PATH;
+ private String bazel = null;
+
+ private String getBazelPath() throws BazelNotFoundException {
+ if (bazel == null) {
+ throw new BazelNotFoundException.BazelNotSetException();
+ }
+ return bazel;
+ }
/**
- * Set the path to the Bazel binary (/usr/local/bin/bazel by default).
+ * Set the path to the Bazel binary.
*/
public synchronized void setBazelPath(String bazel) {
this.bazel = bazel;
}
/**
+ * Check the version of Bazel: throws an exception if the version is incorrect or the path does
+ * not point to a Bazel binary.
+ */
+ public static void checkVersion(String bazel) throws BazelNotFoundException {
+ File path = new File(bazel);
+ if (!path.exists() || !path.canExecute()) {
+ throw new BazelNotFoundException.BazelNotExecutableException();
+ }
+ try {
+ Command command = Command.builder().setConsoleName(null).setDirectory(ASPECT_WORKSPACE)
+ .addArguments(bazel, "version")
+ .setStdoutLineSelector((s) -> s.startsWith("Build label:") ? s.substring(13) : null)
+ .build();
+ if (command.run() != 0) {
+ throw new BazelNotFoundException.BazelNotExecutableException();
+ }
+ List<String> result = command.getSelectedOutputLines();
+ if (result.size() != 1) {
+ throw new BazelNotFoundException.BazelTooOldException("unknown");
+ }
+ String version = result.get(0);
+ Matcher versionMatcher = VERSION_PATTERN.matcher(version);
+ if (versionMatcher == null || !versionMatcher.matches()) {
+ throw new BazelNotFoundException.BazelTooOldException(version);
+ }
+ int[] versionNumbers = {Integer.parseInt(versionMatcher.group(1)),
+ Integer.parseInt(versionMatcher.group(2)), Integer.parseInt(versionMatcher.group(3))};
+ if (compareVersion(versionNumbers, MINIMUM_BAZEL_VERSION) < 0) {
+ throw new BazelNotFoundException.BazelTooOldException(version);
+ }
+ } catch (IOException | InterruptedException e) {
+ throw new BazelNotFoundException.BazelNotExecutableException();
+ }
+ }
+
+ private static int compareVersion(int[] version1, int[] version2) {
+ for (int i = 0; i < Math.min(version1.length, version2.length); i++) {
+ if (version1[i] < version2[i]) {
+ return -1;
+ } else if (version1[i] > version2[i]) {
+ return 1;
+ }
+ }
+ return Integer.compare(version1.length, version2.length);
+ }
+
+ /**
* Returns a {@link BazelInstance} for the given directory. It looks for the enclosing workspace
* and returns the instance that correspond to it. If not in a workspace, returns null.
+ *
+ * @throws BazelNotFoundException
*/
- public BazelInstance getInstance(File directory) throws IOException, InterruptedException {
+ public BazelInstance getInstance(File directory)
+ throws IOException, InterruptedException, BazelNotFoundException {
File workspaceRoot = getWorkspaceRoot(directory);
if (workspaceRoot == null) {
return null;
@@ -98,7 +160,8 @@
private final Map<String, ImmutableMap<String, IdeBuildInfo>> buildInfoCache = new HashMap<>();
- private BazelInstance(File workspaceRoot) throws IOException, InterruptedException {
+ private BazelInstance(File workspaceRoot)
+ throws IOException, InterruptedException, BazelNotFoundException {
this.workspaceRoot = workspaceRoot;
this.packagePath =
String.join("", runBazel("info", "package_path")) + ":" + ASPECT_WORKSPACE.toString();
@@ -107,9 +170,11 @@
/**
* Returns the list of targets present in the BUILD files for the given sub-directories.
+ *
+ * @throws BazelNotFoundException
*/
public synchronized List<String> listTargets(File... directories)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
StringBuilder builder = new StringBuilder();
for (File f : directories) {
builder.append(f.toURI().relativize(workspaceRoot.toURI()).getPath()).append("/... ");
@@ -118,21 +183,23 @@
}
private synchronized List<String> runBazel(String... args)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
return runBazel(ImmutableList.<String>builder().add(args).build());
}
private synchronized List<String> runBazel(List<String> args)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
return BazelCommand.this.runBazelAndGetOuputLines(ConsoleType.WORKSPACE, workspaceRoot, args);
}
/**
* Returns the IDE build information from running the aspect over the given list of targets. The
* result is a list of of path to the output artifact created by the build.
+ *
+ * @throws BazelNotFoundException
*/
private synchronized List<String> buildIdeInfo(Collection<String> targets)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
return BazelCommand.this.runBazelAndGetErrorLines(ConsoleType.WORKSPACE, workspaceRoot,
ImmutableList.<String>builder().add("build").add("--package_path", packagePath)
.addAll(ASPECT_OPTIONS).addAll(targets).build(),
@@ -148,9 +215,11 @@
* <p>
* This method cache it results and won't recompute a previously computed version unless
* {@link #markAsDirty()} has been called in between.
+ *
+ * @throws BazelNotFoundException
*/
public synchronized ImmutableMap<String, IdeBuildInfo> getIdeInfo(Collection<String> targets)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
String key = NEW_LINE_JOINER.join(targets);
if (!buildInfoCache.containsKey(key)) {
buildInfoCache.put(key, IdeBuildInfo.getInfo(buildIdeInfo(targets)));
@@ -172,9 +241,11 @@
/**
* Build a list of targets in the current workspace.
+ *
+ * @throws BazelNotFoundException
*/
public synchronized int build(List<String> targets, String... extraArgs)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
return BazelCommand.this.runBazel(workspaceRoot,
ImmutableList.<String>builder().add("build", "--package_path", packagePath)
.addAll(BUILD_OPTIONS).add(extraArgs).addAll(targets).build());
@@ -182,9 +253,11 @@
/**
* Run test on a list of targets in the current workspace.
+ *
+ * @throws BazelNotFoundException
*/
public synchronized int tests(List<String> targets, String... extraArgs)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
return BazelCommand.this.runBazel(workspaceRoot,
ImmutableList.<String>builder().add("test").add("--package_path", packagePath)
.addAll(BUILD_OPTIONS).add(extraArgs).addAll(targets).build());
@@ -207,8 +280,11 @@
/**
* Gives a list of target completions for the given beginning string. The result is the list of
* possible completion for a target pattern starting with string.
+ *
+ * @throws BazelNotFoundException
*/
- public ImmutableList<String> complete(String string) throws IOException, InterruptedException {
+ public ImmutableList<String> complete(String string)
+ throws IOException, InterruptedException, BazelNotFoundException {
if (string.equals("/") || string.isEmpty()) {
return ImmutableList.of("//");
} else if (string.contains(":")) {
@@ -267,7 +343,8 @@
}
}
- private File getWorkspaceRoot(File directory) throws IOException, InterruptedException {
+ private File getWorkspaceRoot(File directory)
+ throws IOException, InterruptedException, BazelNotFoundException {
List<String> result = runBazelAndGetOuputLines(ConsoleType.SYSTEM, directory,
ImmutableList.of("info", "workspace"));
if (result.size() > 0) {
@@ -277,15 +354,15 @@
}
private ImmutableList<String> runBazelAndGetOuputLines(ConsoleType type, File directory,
- List<String> args) throws IOException, InterruptedException {
+ List<String> args) throws IOException, InterruptedException, BazelNotFoundException {
return runBazelAndGetOuputLines(type, directory, args, (t) -> t);
}
private synchronized ImmutableList<String> runBazelAndGetOuputLines(ConsoleType type,
File directory, List<String> args, Function<String, String> selector)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
Command command = Command.builder().setConsoleName(getConsoleName(type, directory))
- .setDirectory(directory).addArguments(bazel.toString()).addArguments(args)
+ .setDirectory(directory).addArguments(getBazelPath()).addArguments(args)
.setStdoutLineSelector(selector).build();
if (command.run() == 0) {
return command.getSelectedOutputLines();
@@ -295,9 +372,9 @@
private synchronized ImmutableList<String> runBazelAndGetErrorLines(ConsoleType type,
File directory, List<String> args, Function<String, String> selector)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException, BazelNotFoundException {
Command command = Command.builder().setConsoleName(getConsoleName(type, directory))
- .setDirectory(directory).addArguments(bazel.toString()).addArguments(args)
+ .setDirectory(directory).addArguments(getBazelPath()).addArguments(args)
.setStderrLineSelector(selector).build();
if (command.run() == 0) {
return command.getSelectedErrorLines();
@@ -306,13 +383,15 @@
}
private synchronized int runBazel(ConsoleType type, File directory, List<String> args,
- OutputStream stdout, OutputStream stderr) throws IOException, InterruptedException {
+ OutputStream stdout, OutputStream stderr)
+ throws IOException, InterruptedException, BazelNotFoundException {
return Command.builder().setConsoleName(getConsoleName(type, directory)).setDirectory(directory)
- .addArguments(bazel.toString()).addArguments(args).setStandardOutput(stdout)
+ .addArguments(getBazelPath()).addArguments(args).setStandardOutput(stdout)
.setStandardError(stderr).build().run();
}
- private int runBazel(File directory, List<String> args) throws IOException, InterruptedException {
+ private int runBazel(File directory, List<String> args)
+ throws IOException, InterruptedException, BazelNotFoundException {
return runBazel(ConsoleType.WORKSPACE, directory, args, null, null);
}
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/BazelNotFoundException.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/BazelNotFoundException.java
new file mode 100644
index 0000000..e67bc20
--- /dev/null
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/BazelNotFoundException.java
@@ -0,0 +1,36 @@
+package com.google.devtools.bazel.e4b.command;
+
+public class BazelNotFoundException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private BazelNotFoundException(String msg) {
+ super(msg);
+ }
+
+ public static final class BazelNotSetException extends BazelNotFoundException {
+ private static final long serialVersionUID = 1L;
+
+ public BazelNotSetException() {
+ super("Path to Bazel binary is not set, please set it "
+ + "(Preferences... > Bazel Plugins Preferences)");
+ }
+ }
+
+ public static final class BazelNotExecutableException extends BazelNotFoundException {
+ private static final long serialVersionUID = 1L;
+
+ public BazelNotExecutableException() {
+ super("Path to Bazel is wrong (does not point to a binary), please set it "
+ + "(Preferences... > Bazel Plugins Preferences)");
+ }
+ }
+
+ public static final class BazelTooOldException extends BazelNotFoundException {
+ private static final long serialVersionUID = 1L;
+
+ public BazelTooOldException(String version) {
+ super("Bazel version (" + version + ") is unsupported (too old or development version), "
+ + "please update your Bazel binary.");
+ }
+ }
+}
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/preferences/BazelPreferencePage.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/preferences/BazelPreferencePage.java
index 1619e80..02ffad9 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/preferences/BazelPreferencePage.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/preferences/BazelPreferencePage.java
@@ -14,13 +14,15 @@
package com.google.devtools.bazel.e4b.preferences;
+import com.google.devtools.bazel.e4b.Activator;
+import com.google.devtools.bazel.e4b.command.BazelCommand;
+import com.google.devtools.bazel.e4b.command.BazelNotFoundException;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.FileFieldEditor;
+import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
-import com.google.devtools.bazel.e4b.Activator;
-
/**
* Page to configure the e4b plugin. The only configuration parameter is the path to the Bazel
* binary so this page provide a file field to specify it.
@@ -29,13 +31,30 @@
implements
IWorkbenchPreferencePage {
+ private static class BazelBinaryFieldEditor extends FileFieldEditor {
+ BazelBinaryFieldEditor(Composite parent) {
+ super("BAZEL_PATH", "Path to the &Bazel binary:", true, parent);
+ setValidateStrategy(VALIDATE_ON_KEY_STROKE);
+ }
+
+ @Override
+ protected boolean doCheckState() {
+ try {
+ BazelCommand.checkVersion(getTextControl().getText());
+ return true;
+ } catch (BazelNotFoundException e) {
+ setErrorMessage(e.getMessage());
+ return false;
+ }
+ }
+ }
+
public BazelPreferencePage() {
super(GRID);
}
public void createFieldEditors() {
- addField(new FileFieldEditor("BAZEL_PATH", "Path to the &Bazel binary:", true,
- getFieldEditorParent()));
+ addField(new BazelBinaryFieldEditor(getFieldEditorParent()));
}
@Override
@@ -43,5 +62,4 @@
setPreferenceStore(Activator.getDefault().getPreferenceStore());
setDescription("Bazel plugin settings");
}
-
}
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/wizard/BazelTargetCompletionContentProposalProvider.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/wizard/BazelTargetCompletionContentProposalProvider.java
index ef69e60..a67eaf9 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/wizard/BazelTargetCompletionContentProposalProvider.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/wizard/BazelTargetCompletionContentProposalProvider.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableList;
import com.google.devtools.bazel.e4b.Activator;
import com.google.devtools.bazel.e4b.command.BazelCommand.BazelInstance;
+import com.google.devtools.bazel.e4b.command.BazelNotFoundException;
/**
* A {@link IContentProposalProvider} to provide completion for Bazel. Use the
@@ -53,6 +54,8 @@
Activator.error("Failed to run Bazel to get completion information", e);
} catch (InterruptedException e) {
Activator.error("Bazel was interrupted", e);
+ } catch (BazelNotFoundException e) {
+ Activator.error("Bazel not found: " + e.getMessage());
}
return null;
}
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/wizard/WorkspaceWizardPage.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/wizard/WorkspaceWizardPage.java
index 350e596..bb4da5b 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/wizard/WorkspaceWizardPage.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/wizard/WorkspaceWizardPage.java
@@ -42,6 +42,7 @@
import com.google.common.collect.ImmutableList;
import com.google.devtools.bazel.e4b.Activator;
+import com.google.devtools.bazel.e4b.command.BazelNotFoundException;
/**
* This is a quick wizard page that ask the user for the various targets and source path he wants to
@@ -167,6 +168,8 @@
getWorkspaceRoot() + " does not seems to be a Bazel workspace");
} catch (InterruptedException e1) {
Activator.error("Bazel was interrupted", e1);
+ } catch (BazelNotFoundException e1) {
+ MessageDialog.openError(getShell(), "Error", "Cannot found Bazel: " + e1.getMessage());
}
}