diff --git a/com.salesforce.b2eclipse.jdt.ls/resources/bzleclipse_aspect.bzl b/com.salesforce.b2eclipse.jdt.ls/resources/bzleclipse_aspect.bzl index f1d1766..3d4dce8 100644 --- a/com.salesforce.b2eclipse.jdt.ls/resources/bzleclipse_aspect.bzl +++ b/com.salesforce.b2eclipse.jdt.ls/resources/bzleclipse_aspect.bzl @@ -14,7 +14,6 @@ # limitations under the License. # - # Aspect for Bazel Eclipse Feature, taken from an early version of intellij_info.bzl # TODO upgrade this to their latest work diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/config/BazelEclipseProjectFactory.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/config/BazelEclipseProjectFactory.java index 8f425cd..2c3f3d7 100644 --- a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/config/BazelEclipseProjectFactory.java +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/config/BazelEclipseProjectFactory.java @@ -77,7 +77,7 @@ import com.salesforce.b2eclipse.model.AspectPackageInfo; import com.salesforce.b2eclipse.model.AspectPackageInfos; import com.salesforce.b2eclipse.model.BazelLabel; -import com.salesforce.b2eclipse.model.BazelPackageInfo; +import com.salesforce.bazel.sdk.model.BazelPackageInfo; import com.salesforce.b2eclipse.runtime.api.ResourceHelper; /** diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/config/ImportOrderResolver.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/config/ImportOrderResolver.java index b61e951..e2909a5 100644 --- a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/config/ImportOrderResolver.java +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/config/ImportOrderResolver.java @@ -31,7 +31,7 @@ import com.google.common.graph.Traverser; import com.salesforce.b2eclipse.model.AspectPackageInfo; import com.salesforce.b2eclipse.model.AspectPackageInfos; -import com.salesforce.b2eclipse.model.BazelPackageInfo; +import com.salesforce.bazel.sdk.model.BazelPackageInfo; final class ImportOrderResolver { diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/importer/BazelProjectImportScanner.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/importer/BazelProjectImportScanner.java index 1ad0003..7882e7a 100644 --- a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/importer/BazelProjectImportScanner.java +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/importer/BazelProjectImportScanner.java @@ -46,7 +46,7 @@ import com.salesforce.b2eclipse.BazelJdtPlugin; import com.salesforce.b2eclipse.command.BazelCommandManager; import com.salesforce.b2eclipse.command.BazelWorkspaceCommandRunner; -import com.salesforce.b2eclipse.model.BazelPackageInfo; +import com.salesforce.bazel.sdk.model.BazelPackageInfo; import com.salesforce.b2eclipse.runtime.impl.EclipseWorkProgressMonitor; /** diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/managers/BazelProjectImporter.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/managers/BazelProjectImporter.java index 26a9790..efbfdc1 100644 --- a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/managers/BazelProjectImporter.java +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/managers/BazelProjectImporter.java @@ -34,9 +34,14 @@ package com.salesforce.b2eclipse.managers; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import org.eclipse.core.runtime.CoreException; @@ -44,18 +49,19 @@ import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jdt.ls.core.internal.AbstractProjectImporter; -import com.salesforce.b2eclipse.BazelJdtPlugin; import com.salesforce.b2eclipse.abstractions.WorkProgressMonitor; -import com.salesforce.b2eclipse.command.BazelCommandManager; import com.salesforce.b2eclipse.config.BazelEclipseProjectFactory; -import com.salesforce.b2eclipse.importer.BazelProjectImportScanner; -import com.salesforce.b2eclipse.model.BazelPackageInfo; +import com.salesforce.bazel.sdk.model.BazelPackageInfo; import com.salesforce.b2eclipse.runtime.impl.EclipseWorkProgressMonitor; +import com.salesforce.bazel.sdk.project.ProjectView; +import com.salesforce.bazel.sdk.workspace.BazelWorkspaceScanner; @SuppressWarnings("restriction") public final class BazelProjectImporter extends AbstractProjectImporter { private static final String WORKSPACE_FILE_NAME = "WORKSPACE"; + + private static final String BAZELPROJECT_FILE_NAME = ".bazelproject"; @Override public boolean applies(IProgressMonitor monitor) throws OperationCanceledException, CoreException { @@ -79,26 +85,52 @@ public boolean applies(IProgressMonitor monitor) throws OperationCanceledExcepti @Override public void importToWorkspace(IProgressMonitor monitor) throws OperationCanceledException, CoreException { - BazelCommandManager bazelCommandManager = BazelJdtPlugin.getBazelCommandManager(); - BazelProjectImportScanner scanner = new BazelProjectImportScanner(bazelCommandManager, rootFolder); - - BazelPackageInfo workspaceRootPackage = scanner.getProjects(monitor); - + try { + BazelWorkspaceScanner workspaceScanner = new BazelWorkspaceScanner(); + BazelPackageInfo workspaceRootPackage = workspaceScanner.getPackages(rootFolder); + if (workspaceRootPackage == null) { throw new IllegalArgumentException(); } - List bazelPackagesToImport = - workspaceRootPackage.getChildPackageInfos().stream().collect(Collectors.toList()); + + List allBazelPackages = new ArrayList<>( + workspaceRootPackage.getChildPackageInfos() + ); + + List bazelPackagesToImport = allBazelPackages; + + File targetsFile = new File(rootFolder, BAZELPROJECT_FILE_NAME); + + if (targetsFile.exists()) { + ProjectView projectView = new ProjectView(rootFolder, readFile(targetsFile.getPath())); + + Set projectViewPaths = projectView.getDirectories().stream() + .map(p -> p.getBazelPackageFSRelativePath()).collect(Collectors.toSet()); + + bazelPackagesToImport = allBazelPackages.stream().filter(bpi -> projectViewPaths.contains(bpi.getBazelPackageFSRelativePath())) + .collect(Collectors.toList()); + } WorkProgressMonitor progressMonitor = new EclipseWorkProgressMonitor(null); BazelEclipseProjectFactory.importWorkspace(workspaceRootPackage, bazelPackagesToImport, progressMonitor, monitor); + } catch (IOException e) { + // TODO: proper handling here + } } @Override public void reset() { } + + private static String readFile(String path) { + try { + return new String(Files.readAllBytes(Paths.get(path))); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } } diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/BasicLoggerFacade.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/BasicLoggerFacade.java new file mode 100644 index 0000000..1398d96 --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/BasicLoggerFacade.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.salesforce.bazel.sdk.logging; + +// we will revisit this later, see https://github.com/salesforce/bazel-eclipse/issues/10 +// import org.slf4j.LoggerFactory; + +/** + * + * Default facade that crudely logs to stdout/stderr. + * + */ +public class BasicLoggerFacade extends LoggerFacade { + + @Override + public void error(Class from, String message, Object... args) { + // LoggerFactory.getLogger(from).error(message, args); + System.err.println(formatMsg(from, message, args)); + } + + @Override + public void error(Class from, String message, Throwable exception, Object... args) { + //LoggerFactory.getLogger(from).error(message, exception, args); + System.err.println(formatMsg(from, message, args)); + } + + @Override + public void warn(Class from, String message, Object... args) { + // LoggerFactory.getLogger(from).warn(message, args); + System.out.println(formatMsg(from, message, args)); + } + + @Override + public void info(Class from, String message, Object... args) { + // LoggerFactory.getLogger(from).info(message, args); + System.out.println(formatMsg(from, message, args)); + } + + @Override + public void debug(Class from, String message, Object... args) { + // LoggerFactory.getLogger(from).debug(message, args); + // System.out.println(formatMsg(from, message, args)); + } + + private String formatMsg(Class from, String message, Object... args) { + return "[" + from.getName() + "] " + message; + } + +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/LogHelper.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/LogHelper.java new file mode 100644 index 0000000..b7fa48e --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/LogHelper.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.salesforce.bazel.sdk.logging; + +/** + * Helper to log messages. Doesn't cache anything but class name for common logging frameworks. This allows the + * LoggerFacade to be changed and without having to constantly give the class. + * + * This is the preferred way to log. + * + * @author Blaine Buxton + * + */ +public class LogHelper { + final Class from; + + public static LogHelper log(Class from) { + return new LogHelper(from); + } + + private LogHelper(Class from) { + this.from = from; + } + + public void error(String message, Object... args) { + getFacade().error(from, message, args); + } + + public void error(String message, Throwable exception, Object... args) { + getFacade().error(from, message, exception, args); + } + + public void warn(String message, Object... args) { + getFacade().warn(from, message, args); + } + + public void info(String message, Object... args) { + getFacade().info(from, message, args); + } + + public void debug(String message, Object... args) { + getFacade().debug(from, message, args); + } + + private LoggerFacade getFacade() { + return LoggerFacade.instance(); + } +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/LoggerFacade.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/LoggerFacade.java new file mode 100644 index 0000000..af89c05 --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/logging/LoggerFacade.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.salesforce.bazel.sdk.logging; + +/** + * Logger facade. + * + * This is an interface so that tests can use it and verify logging + */ +public abstract class LoggerFacade { + static LoggerFacade instance = new BasicLoggerFacade(); + + /** + * Default instance, this can change - DO NOT CACHE or STORE + * + * @return + */ + public static LoggerFacade instance() { + return instance; + } + + /** + * log error + * + * @param from + * @param message + * @param args + */ + public abstract void error(Class from, String message, Object... args); + + /** + * Log error with exception stack trace + * + * @param from + * @param message + * @param exception + * @param args + */ + public abstract void error(Class from, String message, Throwable exception, Object... args); + + /** + * Log warn message + * + * @param from + * @param message + * @param args + */ + public abstract void warn(Class from, String message, Object... args); + + /** + * Log info message + * + * @param from + * @param message + * @param args + */ + public abstract void info(Class from, String message, Object... args); + + /** + * Log debug message + * + * @param from + * @param message + * @param args + */ + public abstract void debug(Class from, String message, Object... args); + + public static void setInstance(LoggerFacade newFacade) { + instance = newFacade; + } + +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelBuildFileHelper.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelBuildFileHelper.java new file mode 100644 index 0000000..8274b1e --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelBuildFileHelper.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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.salesforce.bazel.sdk.model; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; + +import com.google.common.annotations.VisibleForTesting; +import com.salesforce.bazel.sdk.logging.LogHelper; + +public class BazelBuildFileHelper { + static final LogHelper LOG = LogHelper.log(BazelBuildFileHelper.class); + /** + * List of Strings that can be found in BUILD files that will indicate a Bazel package that is supported by the + * Eclipse plugin. Currently, only Java packages are supported. + *

+ * The line must begin with one of these tokens (leading whitespace is ignored). This prevents false positives when + * comments include one of these tokens. Also, this means that just loading a Java rule in a load() statement is not + * enough to trigger the detector. + */ + public static final String[] JAVA_PROJECT_INDICATORS = + { "java_binary", "java_library", "java_test", "java_web_test_suite", "springboot", "springboot_test", + "java_proto_library", "java_lite_proto_library", "java_grpc_library" }; + + /** + * Parses a File, presumed to be a Bazel BUILD file, looking for indications that it contains Java rules. + * + * @param buildFile + * @return true if it contains at least one Java rule, false if not + */ + public static boolean hasJavaRules(File buildFile) { + boolean hasJavaRules = false; + + if (!buildFile.exists() || !buildFile.canRead()) { + return false; + } + + try (InputStream is = new FileInputStream(buildFile)) { + hasJavaRules = hasJavaRules(is); + } catch (Exception anyE) { + LOG.error(anyE.getMessage(), anyE); + } + return hasJavaRules; + } + + /** + * Parses an InputStream, presumed to be the contents of a Bazel BUILD file, looking for indications that it + * contains Java rules. + * + * @param is + * @return true if it contains at least one Java rule, false if not + */ + public static boolean hasJavaRules(InputStream is) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { + String buildFileLine = br.readLine(); + while (buildFileLine != null) { + if (hasJavaRulesInLine(buildFileLine)) { + return true; + } + buildFileLine = br.readLine(); + } + + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + return false; + } + + @VisibleForTesting + static boolean hasJavaRulesInLine(String buildFileLine) { + buildFileLine = buildFileLine.trim(); + if (Arrays.stream(JAVA_PROJECT_INDICATORS).parallel().anyMatch(buildFileLine::startsWith)) { + return true; + } + return false; + } +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelLabel.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelLabel.java new file mode 100644 index 0000000..d8a28e0 --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelLabel.java @@ -0,0 +1,317 @@ +/** + * Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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 + * 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.salesforce.bazel.sdk.model; + +/** + * Answers to everything you've always wanted to ask a Bazel Label. + *

+ * Pass this around in code instead of String primitives. + *

+ * IMPORTANT NOTE internally this class assumes that '/' is the path separator used in label/target names. This + * will most likely need to get fixed to support running on Windows. + *

+ * + * @author stoens + * @since Hawaii 2019 + */ +public class BazelLabel { + + private final String localLabelPart; + private final String repositoryName; + private final String fullLabel; + + /** + * A BazelLabel instance can be created with any syntactically valid Bazel Label String. + *

+ * Examples:
+ * //foo/blah:t1
+ * //foo
+ * blah/...
+ */ + public BazelLabel(String label) { + if (label == null) { + throw new IllegalArgumentException("label cannot be null"); + } + if (label.startsWith("@")) { + int i = label.indexOf("//"); + this.repositoryName = label.substring(1, i); + label = label.substring(i); + } else { + this.repositoryName = null; + } + label = sanitizeLabel(label); + this.localLabelPart = label; + this.fullLabel = getFullLabel(this.repositoryName, this.localLabelPart); + } + + /** + * Instantiates a BazelLabel instance with the Bazel package path and the label + * name specified separately. For example: "a/b/c" and "my-target-name". + */ + public BazelLabel(String packagePath, String targetName) { + this(sanitizePackagePath(packagePath) + ":" + sanitizeTargetName(targetName)); + } + + /** + * Returns the label as a String. + * + * @return the label + */ + public String getLabel() { + return fullLabel; + } + + /** + * Returns the repository of this label, null if no repository was specified for this label. + * + * @return the repository name, without the leading '@' and trailing "//" + */ + public String getRepositoryName() { + return repositoryName; + } + + + /** + * If a label omits the target name it refers to and it doesn't use wildcard syntax, it refers to the + * package-default target. This is that target that has the same name as the Bazel Package it lives in. + * + * @return true if this instance points to the package default target, false otherwise + */ + public boolean isPackageDefault() { + if (!isConcrete()) { + return false; + } + int i = this.localLabelPart.lastIndexOf(":"); + return i == -1; + } + + /** + * If a label refers to a single Bazel Target, is it concrete. If it using wildcard syntax, it is not concrete. + * + * @return true if this instance represents a concrete label, false otherwise + */ + public boolean isConcrete() { + return !(this.localLabelPart.endsWith("*") || + this.localLabelPart.endsWith("...") || + this.localLabelPart.endsWith("all")); + } + + /** + * Returns the package path of this label, which is the "path part" of the label, excluding any specific target or + * target wildcard pattern. + * + * For example, given a label //foo/blah/goo:t1, the package path is foo/blah/goo. + * + * @return the package path of this label + */ + public String getPackagePath() { + String packagePath = this.localLabelPart; + int i = packagePath.lastIndexOf("..."); + if (i != -1) { + packagePath = packagePath.substring(0, i); + if (packagePath.endsWith("/")) { + packagePath = packagePath.substring(0, packagePath.length() - 1); + } + } else { + i = this.localLabelPart.lastIndexOf(":"); + if (i != -1) { + packagePath = packagePath.substring(0, i); + } + } + return packagePath; + } + + /** + * Returns the default package label for this label. The default package label does not specify an explicit target + * and only corresponds to the package path. + * + * For example, given //foo/blah/goo:t1, the corresponding default package label is //foo/blah/goo. + * + * @return BazelLabel instance representing the default package label. + * @throws IllegalArgumentException + * if this label is a root-level label (//...) and therefore doesn't have a package path. + */ + public BazelLabel toDefaultPackageLabel() { + return withRepositoryNameAndLocalLabelPart(repositoryName, getPackagePath()); + } + + /** + * Returns the package name of this label, which is the right-most path component of the package path. + * + * For example, given a label //foo/blah/goo:t1, the package name is goo. + * + * @return the package name of this label + */ + public String getPackageName() { + String packagePath = getPackagePath(); + int i = packagePath.lastIndexOf("/"); + return i == -1 ? packagePath : packagePath.substring(i + 1); + } + + /** + * Returns the target part of this label. + * + * @return the target name this label refers to, null if this label uses "..." syntax. + */ + public String getTargetName() { + if (localLabelPart.endsWith("...")) { + return null; + } + if (isPackageDefault()) { + return getPackageName(); + } else { + int i = localLabelPart.lastIndexOf(":"); + // label cannot end with ":", so this is ok + return localLabelPart.substring(i + 1); + } + } + + /** + * Some Bazel Target names use a path-like syntax. This method returns the last component of that path. If the + * target name doesn't use a path-like syntax, this method returns the target name. + * + * For example: if the target name is "a/b/c/d", this method returns "d". if the target name is "a/b/c/", this + * method returns "c". if the target name is "foo", this method returns "foo". + * + * @return the last path component of the target name if the target name is path-like + */ + public String getLastComponentOfTargetName() { + String targetName = getTargetName(); + if (targetName == null) { + return null; + } + int i = targetName.lastIndexOf("/"); + if (i != -1) { + return targetName.substring(i + 1); // ok because target name cannot end with '/' + } + return targetName; + } + + /** + * Adds package wildcard syntax to a package default label. + * + * For example: //foo/blah -> //foo:blah:* + * + * @return a new BazelLabel instance, with added package wildcard syntax + * @throws IllegalStateException + * if this is not a package default label + */ + public BazelLabel toPackageWildcardLabel() { + if (!isPackageDefault()) { + throw new IllegalStateException("label " + this.localLabelPart + " is not package default"); + } + return withRepositoryNameAndLocalLabelPart(repositoryName, getPackagePath() + ":*"); + } + + @Override + public int hashCode() { + return fullLabel.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof BazelLabel) { + BazelLabel o = (BazelLabel) other; + return fullLabel.equals(o.fullLabel); + } + return false; + } + + @Override + public String toString() { + return this.fullLabel; + } + + private static BazelLabel withRepositoryNameAndLocalLabelPart(String repositoryName, String localLabelPart) { + return repositoryName == null ? + new BazelLabel(localLabelPart) : + new BazelLabel("@" + repositoryName + "//" + localLabelPart); + } + + private static String sanitizePackagePath(String path) { + if (path == null) { + throw new IllegalAccessError(path); + } + path = path.trim(); + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + return path; + } + + private static String sanitizeTargetName(String target) { + if (target == null) { + throw new IllegalArgumentException(target); + } + target = target.trim(); + if (target.startsWith(":")) { + target = target.substring(1); + } + return target; + } + + private static String sanitizeLabel(String label) { + if (label == null) { + throw new IllegalArgumentException(label); + } + label = label.trim(); + if (label.length() == 0) { + throw new IllegalArgumentException(label); + } + if (label.endsWith(":")) { + throw new IllegalArgumentException(label); + } + if (label.endsWith("/")) { + throw new IllegalArgumentException(label); + } + if (label.equals("//")) { + throw new IllegalArgumentException(label); + } + if (label.startsWith("//")) { + label = label.substring(2); + } + if (label.startsWith("/")) { + label = label.substring(1); + } + return label; + } + + private static String getFullLabel(String repositoryName, String localLabelPart) { + return (repositoryName == null ? "" : "@" + repositoryName) + "//" + localLabelPart; + } +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/model/BazelPackageInfo.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelPackageInfo.java similarity index 86% rename from com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/model/BazelPackageInfo.java rename to com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelPackageInfo.java index 72acd71..53f4a88 100644 --- a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/b2eclipse/model/BazelPackageInfo.java +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelPackageInfo.java @@ -20,7 +20,7 @@ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * 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 * @@ -31,15 +31,14 @@ * specific language governing permissions and limitations under the License. * */ -package com.salesforce.b2eclipse.model; +package com.salesforce.bazel.sdk.model; import java.io.File; -import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; /** * Model class for a Bazel Java package. It is a node in a tree of the hierarchy of packages. The root node in this tree @@ -56,21 +55,21 @@ * WORKSPACE root (BazelPackageInfo instance 1)
* //projects/libs/apple (BazelPackageInfo instance 2)
* //projects/libs/banana (BazelPackageInfo instance 3)
- * + * * @author plaird */ -public class BazelPackageInfo { +public class BazelPackageInfo implements BazelPackageLocation { private final String relativeWorkspacePath; private final File directory; private BazelPackageInfo parent; private final boolean isWorkspaceRoot; - private final File workspaceRoot; - private BazelPackageInfo workspaceRootNode; + protected final File workspaceRoot; + protected BazelPackageInfo workspaceRootNode; public static final String WORKSPACE_FILENAME = "WORKSPACE"; - public static final String BUILD_FILENAME = "BUILD"; + public static final String WORKSPACE_FILENAME_ALT = "WORKSPACE.bazel"; private String computedPackageName = null; private String computedPackageNameLastSegment = null; @@ -80,7 +79,7 @@ public class BazelPackageInfo { /** * Creates the root info object for a Bazel workspace. This is not normally associated with an actual Bazel package * (hopefully not), so it is a special case node. All other info nodes descend from this node. - * + * * @param rootDirectory * the file system location that holds the workspace. This directory must have a WORKSPACE file. */ @@ -97,13 +96,11 @@ public BazelPackageInfo(File rootDirectory) { this.workspaceRoot = rootDirectory; File workspaceFile = new File(this.workspaceRoot, WORKSPACE_FILENAME); if (!workspaceFile.exists()) { - throw new IllegalArgumentException("The path [" + rootDirectory.getAbsolutePath() + "] does not contain a " - + WORKSPACE_FILENAME + " file."); - } - - File buildFile = new File(this.workspaceRoot, BUILD_FILENAME); - if (buildFile.exists()) { - throw new IllegalStateException("Root package is not supported. BUILD files should be in subdirectories"); + workspaceFile = new File(this.workspaceRoot, WORKSPACE_FILENAME_ALT); + if (!workspaceFile.exists()) { + throw new IllegalArgumentException("The path [" + rootDirectory.getAbsolutePath() + + "] does not contain a " + WORKSPACE_FILENAME + " file."); + } } this.parent = null; @@ -118,7 +115,7 @@ public BazelPackageInfo(File rootDirectory) { /** * Creates a new info object for a Bazel package - * + * * @param anotherNode * another node of the BazelPackageInfo tree, this cannot be null. The 'best' parent node for this new * node will be found using the passed node's links to the other nodes in the tree. The parent package @@ -161,6 +158,12 @@ public BazelPackageInfo(BazelPackageInfo anotherNode, String relativeWorkspacePa "The path [" + this.directory.getAbsolutePath() + "] contains a " + WORKSPACE_FILENAME + " file. Nested workspaces are not supported by BazelPackageInfo at this time"); } + workspaceFile = new File(this.directory, WORKSPACE_FILENAME_ALT); + if (workspaceFile.exists()) { + throw new IllegalArgumentException( + "The path [" + this.directory.getAbsolutePath() + "] contains a " + WORKSPACE_FILENAME_ALT + + " file. Nested workspaces are not supported by BazelPackageInfo at this time"); + } // compute and cache the package name String packageName = getBazelPackageName(); @@ -197,18 +200,20 @@ public Collection getChildPackageInfos() { /** * Is this node the workspace root? - * + * * @return true if the root, false otherwise */ + @Override public boolean isWorkspaceRoot() { return this.isWorkspaceRoot; } /** * Gets the workspace root filesystem directory. - * + * * @return the root directory */ + @Override public File getWorkspaceRootDirectory() { // now is a good time to check that the root directory is still there if (!this.workspaceRoot.exists()) { @@ -221,7 +226,7 @@ public File getWorkspaceRootDirectory() { /** * Gets the WORKSPACE file - * + * * @return the file */ public File getWorkspaceFile() { @@ -244,7 +249,7 @@ public File getWorkspaceFile() { * Returns the absolute file system path of the package in the workspace. The separator char will be the OS file * separator. *

- * + * * e.g. "/home/joe/dev/projects/libs/apple" or "C:\dev\projects\libs\apple" */ public String getBazelPackageFSAbsolutePath() { @@ -256,9 +261,10 @@ public String getBazelPackageFSAbsolutePath() { * Returns the relative file system path of the package in the workspace. The separator char will be the OS file * separator. *

- * + * * e.g. "projects/libs/apple" or "projects\libs\apple" */ + @Override public String getBazelPackageFSRelativePath() { return relativeWorkspacePath; } @@ -287,9 +293,10 @@ public String getBazelPackageFSRelativePathForUI() { /** * Provides the proper Bazel label for the Bazel package. *

- * + * * e.g. "//projects/libs/apple" */ + @Override public String getBazelPackageName() { if (computedPackageName != null) { return computedPackageName; @@ -299,18 +306,28 @@ public String getBazelPackageName() { // the caller is referring to the WORKSPACE root, which for build operations can // (but not always) means that the user wants to build the entire workspace. - // TODO refine this, so that if the root directory contains a BUILD file with a Java package to - // somehow handle that workspace differently + // TODO refine this, so that if the root directory contains a BUILD file with a Java package to + // somehow handle that workspace differently // Docs should indicate that a better practice is to keep the root dir free of an actual package // For now, assume that anything referring to the root dir is a proxy for 'whole repo' computedPackageName = "//..."; return computedPackageName; } - // set computedPackageName only when done computing it, to avoid threading issues - computedPackageName = StreamSupport.stream(Paths.get(relativeWorkspacePath).spliterator(), false) - .map(Object::toString).collect(Collectors.joining("/", "//", "")); + // split the file system path by OS path separator + String[] pathElements = relativeWorkspacePath.split(File.separator); + + // assemble the path elements into a proper Bazel package name + String name = "/"; + for (String e : pathElements) { + if (e.isEmpty()) { + continue; + } + name = name + "/" + e; + } + // set computedPackageName only when done computing it, to avoid threading issues + computedPackageName = name; // and cache the last segment as well getBazelPackageNameLastSegment(); @@ -322,6 +339,7 @@ public String getBazelPackageName() { *

* e.g. if "//projects/libs/apple" is the package name, will return 'apple' */ + @Override public String getBazelPackageNameLastSegment() { if (computedPackageNameLastSegment != null) { return computedPackageNameLastSegment; @@ -343,7 +361,7 @@ public String getBazelPackageNameLastSegment() { /** * Find a node in the tree that has the passed Bazel package path - * + * * @param bazelPackagePath * path to find, such as //projects/libs/apple * @return the node if found, or null @@ -391,6 +409,23 @@ private BazelPackageInfo findBestParent(BazelPackageInfo candidate) { return null; } + @Override + public List gatherChildren() { + List gatherList = new ArrayList<>(); + gatherChildrenRecur(gatherList); + return gatherList; + } + + public void gatherChildrenRecur(List gatherList) { + if (!this.isWorkspaceRoot()) { + gatherList.add(this); + } + for (BazelPackageLocation child : this.childPackages.values()) { + BazelPackageInfo childInfo = (BazelPackageInfo) child; + childInfo.gatherChildrenRecur(gatherList); + } + } + @Override public int hashCode() { final int prime = 31; @@ -401,23 +436,18 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) { + if (this == obj) return true; - } - if (obj == null) { + if (obj == null) return false; - } - if (getClass() != obj.getClass()) { + if (getClass() != obj.getClass()) return false; - } BazelPackageInfo other = (BazelPackageInfo) obj; if (computedPackageName == null) { - if (other.computedPackageName != null) { + if (other.computedPackageName != null) return false; - } - } else if (!computedPackageName.equals(other.computedPackageName)) { + } else if (!computedPackageName.equals(other.computedPackageName)) return false; - } return true; } diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelPackageLocation.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelPackageLocation.java new file mode 100644 index 0000000..9ea9eba --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/model/BazelPackageLocation.java @@ -0,0 +1,65 @@ +package com.salesforce.bazel.sdk.model; + +import java.io.File; +import java.util.List; + +/** + * Minimal representation of a Bazel Package location on the file system. + * + * @see BazelLabel for a container that is also Bazel Target aware. + * @author stoens + * @since March 2020 + */ +public interface BazelPackageLocation { + + /** + * Returns the name of this Bazel Package - this is name of the final directory in the path. + * + * For example, if this Bazel Package is at the abs path ~/projects/bazel-workspace/a/b/c, this method returns "c". + */ + String getBazelPackageNameLastSegment(); + + /** + * Returns the path of this Bazel Package, relative to the WORKSPACE root directory. + * + * For example, if this Bazel Package is at the abs path ~/projects/bazel-workspace/a/b/c, this method returns + * a/b/c. + */ + String getBazelPackageFSRelativePath(); + + /** + * Returns the abs path of the directory containing the WORKSPACE file for this Bazel Package. + * + * For example, if this Bazel Package is at the abs path ~/projects/bazel-workspace/a/b/c, this method return + * ~/projects/bazel-workspace. + * + */ + File getWorkspaceRootDirectory(); + + /** + * True if this is the root Bazel Package that contains the WORKSPACE file. + */ + boolean isWorkspaceRoot(); + + /** + * Provides the proper Bazel label for the Bazel package. + *

+ * + * e.g. "//projects/libs/apple" + */ + public String getBazelPackageName(); + + /** + * Builds a list containing this node, plus all children (recursively) + */ + public List gatherChildren(); + + /** + * Returns the targets configured for this Bazel Package, at import time. + * + * A null return value indicates that the user did not specify any specific targets. + */ + default public List getBazelTargets() { + return null; + } +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/project/ProjectView.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/project/ProjectView.java new file mode 100644 index 0000000..1621649 --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/project/ProjectView.java @@ -0,0 +1,253 @@ +package com.salesforce.bazel.sdk.project; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.salesforce.bazel.sdk.model.BazelLabel; +import com.salesforce.bazel.sdk.model.BazelPackageLocation; +import com.salesforce.bazel.sdk.util.BazelConstants; + +/** + * The project view file. + *

+ * This implementations currently only supports a subset of the functionality provided by the IntelliJ Bazel Plugin, + * namely these sections: + *

+ *
    + *
  • directories
  • + *
+ * + * Example project view file: + * + *
+ * directories:
+ *   path/to/bazel/package1
+ *   path/to/bazel/package2
+ *
+ * targets:
+ *   # the targets section is optional
+ *   //path/to/bazel/package1:t1
+ * 
+ * + * Exclusions are not supported yet. + * + * @author stoens + * @since March 2020 + */ +public class ProjectView { + + static String DIRECTORIES_SECTION = "directories:"; + static String TARGETS_SECTION = "targets:"; + static String DIRECTORIES_COMMENT = "# Add the directories you want added as source here"; + static String INDENT = " "; + + private final File rootWorkspaceDirectory; + private final Map packageToLineNumber; + private final Map targetToLineNumber; + + /** + * Create a new ProjectView instance with the specified directories and targets. + */ + public ProjectView(File rootWorkspaceDirectory, List directories, List targets) { + this.rootWorkspaceDirectory = rootWorkspaceDirectory; + Map pl = new LinkedHashMap<>(); + Map tl = new LinkedHashMap<>(); + initSections(directories, targets, pl, tl); + this.packageToLineNumber = Collections.unmodifiableMap(pl); + // this may get modified, so the map has to be mutable + this.targetToLineNumber = tl; + } + + /** + * Creates a new ProjectView instance with the specified raw content. + */ + public ProjectView(File rootWorkspaceDirectory, String content) { + this.rootWorkspaceDirectory = rootWorkspaceDirectory; + Map pl = new LinkedHashMap<>(); + Map tl = new LinkedHashMap<>(); + parseSections(content, rootWorkspaceDirectory, pl, tl); + this.packageToLineNumber = Collections.unmodifiableMap(pl); + // this may get modified, so the map has to be mutable + this.targetToLineNumber = tl; + } + + /** + * Returns the raw project view file content. + */ + public String getContent() { + StringBuilder sb = new StringBuilder(); + sb.append(DIRECTORIES_SECTION).append(System.lineSeparator()); + sb.append(INDENT).append(DIRECTORIES_COMMENT).append(System.lineSeparator()); + for (BazelPackageLocation pack : packageToLineNumber.keySet()) { + sb.append(INDENT).append(pack.getBazelPackageFSRelativePath()).append(System.lineSeparator()); + } + if (!targetToLineNumber.isEmpty()) { + sb.append(System.lineSeparator()); + sb.append(TARGETS_SECTION).append(System.lineSeparator()); + for (BazelLabel target : targetToLineNumber.keySet()) { + sb.append(INDENT).append(target.getLabel()).append(System.lineSeparator()); + } + } + return sb.toString(); + } + + /** + * Returns the line number, in the raw project view file content, of the specified bazel package, in the + * "directories" section. + */ + public int getLineNumber(BazelPackageLocation pack) { + Integer lineNumber = packageToLineNumber.get(pack); + if (lineNumber == null) { + throw new IllegalArgumentException("Unknown " + pack); + } + return lineNumber; + } + + /** + * Returns the directories with their targets. + */ + public List getDirectories() { + List updatedPackageLocations = new ArrayList<>(packageToLineNumber.size()); + for (BazelPackageLocation packageInfo : packageToLineNumber.keySet()) { + String directory = packageInfo.getBazelPackageFSRelativePath(); + List targets = getTargetsForDirectory(directory); + updatedPackageLocations.add(new ProjectViewPackageLocation(rootWorkspaceDirectory, directory, targets)); + } + return Collections.unmodifiableList(updatedPackageLocations); + } + + /** + * Returns only the targets from the targets: section. + */ + public List getTargets() { + return Collections.unmodifiableList(new ArrayList<>(targetToLineNumber.keySet())); + } + + /** + * Adds the default targets for each directory that does not have one (or more) entries + * in the "targets:" section. + */ + public void addDefaultTargets() { + List defaultLabels = new ArrayList<>(); + for (BazelPackageLocation directory : packageToLineNumber.keySet()) { + boolean foundLabel = false; + BazelLabel bazelPackage = new BazelLabel(directory.getBazelPackageFSRelativePath()); + for (BazelLabel label : targetToLineNumber.keySet()) { + if (label.getPackagePath().equals(bazelPackage.getPackagePath())) { + foundLabel = true; + break; + } + } + if (!foundLabel) { + for (String target : BazelConstants.DEFAULT_PACKAGE_TARGETS) { + defaultLabels.add(new BazelLabel(bazelPackage.getPackagePath(), target)); + } + } + } + for (BazelLabel dflt : defaultLabels) { + // since this method is used to adjust internal state, it is ok for the + // line number to not be correct. + targetToLineNumber.put(dflt, 0); + } + } + + public File getWorkspaceRootDirectory() { + return rootWorkspaceDirectory; + } + + @Override + public int hashCode() { + return packageToLineNumber.keySet().hashCode() ^ targetToLineNumber.keySet().hashCode(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ProjectView)) { + return false; + } + ProjectView o = (ProjectView)other; + return packageToLineNumber.keySet().equals(o.packageToLineNumber.keySet()) && + targetToLineNumber.keySet().equals(o.targetToLineNumber.keySet()); + } + + private List getTargetsForDirectory(String directory) { + List targets = null; + BazelLabel bazelPackage = new BazelLabel(directory); + for (BazelLabel target : targetToLineNumber.keySet()) { + if (target.getPackagePath().equals(bazelPackage.getPackagePath())) { + if (targets == null) { + targets = new ArrayList<>(); + } + targets.add(target); + } + } + return targets; + } + + private static void initSections(List packages, List targets, + Map packageToLineNumber, + Map targetToLineNumber) + { + // directories: + // # comment + // therefore: + int lineNumber = 3; + for (BazelPackageLocation pack : packages) { + packageToLineNumber.put(pack, lineNumber); + lineNumber += 1; + } + // newline + lineNumber += 1; + for (BazelLabel target : targets) { + targetToLineNumber.put(target, lineNumber); + lineNumber += 1; + } + } + + private static void parseSections(String content, File rootWorkspaceDirectory, + Map packageToLineNumber, + Map targetToLineNumber) + { + boolean withinDirectoriesSection = false; + boolean withinTargetsSection = false; + int lineNumber = 0; + for (String line : content.split(System.lineSeparator())) { + lineNumber += 1; + line = line.trim(); + if (line.isEmpty()) { + continue; + } + if (line.startsWith("#")) { + continue; + } + if (line.equals(DIRECTORIES_SECTION)) { + withinDirectoriesSection = true; + withinTargetsSection = false; + continue; + } else if (line.equals(TARGETS_SECTION)) { + withinDirectoriesSection = false; + withinTargetsSection = true; + continue; + } else if (line.endsWith(":")) { + // some other yet unknown section + withinDirectoriesSection = false; + withinTargetsSection = false; + continue; + } + if (withinDirectoriesSection) { + packageToLineNumber.put(new ProjectViewPackageLocation(rootWorkspaceDirectory, line), lineNumber); + } else if (withinTargetsSection) { + targetToLineNumber.put(new BazelLabel(line), lineNumber); + } + } + } + + +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/project/ProjectViewPackageLocation.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/project/ProjectViewPackageLocation.java new file mode 100644 index 0000000..b269cb5 --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/project/ProjectViewPackageLocation.java @@ -0,0 +1,104 @@ +package com.salesforce.bazel.sdk.project; + +import java.io.File; +import java.util.List; +import java.util.Objects; + +import com.salesforce.bazel.sdk.model.BazelLabel; +import com.salesforce.bazel.sdk.model.BazelPackageLocation; + +/** + * Represents a line in a project view file. + *

+ * TODO having this distinct from BazelPackageInfo adds complexity to the import logic. Revisit whether we can merge it. + * + */ +public class ProjectViewPackageLocation implements BazelPackageLocation { + + private final File workspaceRootDirectory; + private final String packagePath; + private final List targets; + + public ProjectViewPackageLocation(File workspaceRootDirectory, String packagePath) { + this(workspaceRootDirectory, packagePath, null); + } + + ProjectViewPackageLocation(File workspaceRootDirectory, String packagePath, List targets) { + this.workspaceRootDirectory = Objects.requireNonNull(workspaceRootDirectory); + this.packagePath = Objects.requireNonNull(packagePath); + if (new File(this.packagePath).isAbsolute()) { + throw new IllegalArgumentException("[" + packagePath + "] must be relative"); + } + this.targets = targets; + } + + @Override + public String getBazelPackageNameLastSegment() { + return new File(this.packagePath).getName(); + } + + @Override + public String getBazelPackageFSRelativePath() { + return this.packagePath; + } + + @Override + public File getWorkspaceRootDirectory() { + return this.workspaceRootDirectory; + } + + @Override + public boolean isWorkspaceRoot() { + return this.packagePath.isEmpty(); + } + + @Override + public String getBazelPackageName() { + if ("".equals(packagePath)) { + // the caller is referring to the WORKSPACE root, which for build operations can + // (but not always) means that the user wants to build the entire workspace. + + // TODO refine this, so that if the root directory contains a BUILD file with a Java package to + // somehow handle that workspace differently + // Docs should indicate that a better practice is to keep the root dir free of an actual package + // For now, assume that anything referring to the root dir is a proxy for 'whole repo' + return "//..."; + } + return "//" + packagePath; + } + + @Override + public int hashCode() { + return this.workspaceRootDirectory.hashCode() ^ this.packagePath.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof ProjectViewPackageLocation) { + ProjectViewPackageLocation o = (ProjectViewPackageLocation) other; + return this.workspaceRootDirectory.equals(o.workspaceRootDirectory) + && this.packagePath.equals(o.packagePath); + } + return false; + } + + @Override + public String toString() { + return "package path: " + this.packagePath; + } + + @Override + public List gatherChildren() { + // TODO hard to implement, this class is planned for a rework + return null; + } + + @Override + public List getBazelTargets() { + return targets; + } + +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/BazelConstants.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/BazelConstants.java new file mode 100644 index 0000000..20274b6 --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/BazelConstants.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2020, Salesforce.com, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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.salesforce.bazel.sdk.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +public interface BazelConstants { + + /** + * The Bazel BUILD files BEF looks for. + */ + Collection BUILD_FILE_NAMES = + Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList( + new String[]{"BUILD", "BUILD.bazel"}))); + + /** + * The targets configured by default for each imported Bazel package. + */ + Collection DEFAULT_PACKAGE_TARGETS = + Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList( + // "*" includes test _deploy jars, which we currently need for our Eclipse JUnit + // integration to work - unfortunately building those jars can be slow if there + // are many test targets + new String[]{"*"}))); + +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/BazelPathHelper.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/BazelPathHelper.java new file mode 100644 index 0000000..39d336b --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/BazelPathHelper.java @@ -0,0 +1,60 @@ +package com.salesforce.bazel.sdk.util; + +import java.io.File; +import java.io.IOException; + +/** + * Static utilities. + */ +public class BazelPathHelper { + + /** + * Resolve softlinks and other abstractions in the workspace paths. + */ + public static File getCanonicalFileSafely(File directory) { + if (directory == null) { + return null; + } + try { + directory = directory.getCanonicalFile(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + return directory; + } + + /** + * Resolve softlinks and other abstractions in the workspace paths. + */ + public static String getCanonicalPathStringSafely(File directory) { + String path = null; + if (directory == null) { + return null; + } + try { + path = directory.getCanonicalPath(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + if (path == null) { + // fallback to absolute path in case canonical path fails + path = directory.getAbsolutePath(); + } + return path; + } + + /** + * Resolve softlinks and other abstractions in the workspace paths. + */ + public static String getCanonicalPathStringSafely(String path) { + if (path == null) { + return null; + } + try { + path = new File(path).getCanonicalPath(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + return path; + } +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/WorkProgressMonitor.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/WorkProgressMonitor.java new file mode 100644 index 0000000..b6961bd --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/util/WorkProgressMonitor.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.bazel.sdk.util; + +/** + * Abstraction that allows an observer to monitor the progress of work performed during an operation. + */ +public interface WorkProgressMonitor { + + /** + * Notifies that the main task is beginning. This must only be called once on a given progress monitor instance. + * + * @param name + * the name (or description) of the main task + * @param totalWork + * the total number of work units into which the main task is been subdivided. If the value is + * UNKNOWN the implementation is free to indicate progress in a way which doesn't require + * the total number of work units in advance. + */ + public void beginTask(String name, int totalWork); + + /** + * Notifies that the work is done; that is, either the main task is completed or the user canceled it. This method + * may be called more than once (implementations should be prepared to handle this case). + */ + public void done(); + + /** + * Returns whether cancelation of current operation has been requested. Long-running operations should poll to see + * if cancelation has been requested. + * + * @return true if cancellation has been requested, and false otherwise + * @see #setCanceled(boolean) + */ + public boolean isCanceled(); + + /** + * Sets the cancel state to the given value. + * + * @param value + * true indicates that cancelation has been requested (but not necessarily acknowledged); + * false clears this flag + * @see #isCanceled() + */ + public void setCanceled(boolean value); + + /** + * Notifies that a subtask of the main task is beginning. Subtasks are optional; the main task might not have + * subtasks. + * + * @param name + * the name (or description) of the subtask + */ + public void subTask(String name); + + /** + * Notifies that a given number of work unit of the main task has been completed. Note that this amount represents + * an installment, as opposed to a cumulative amount of work done to date. + * + * @param work + * a non-negative number of work units just completed + */ + public void worked(int work); + + WorkProgressMonitor NOOP = new WorkProgressMonitor() { + @Override + public void worked(int work) {} + + @Override + public void subTask(String name) {} + + @Override + public void setCanceled(boolean value) {} + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void done() {} + + @Override + public void beginTask(String name, int totalWork) {} + }; +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/workspace/BazelPackageFinder.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/workspace/BazelPackageFinder.java new file mode 100644 index 0000000..defa99b --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/workspace/BazelPackageFinder.java @@ -0,0 +1,63 @@ +package com.salesforce.bazel.sdk.workspace; + +import java.io.File; +import java.util.Set; + +import com.salesforce.bazel.sdk.logging.LogHelper; +import com.salesforce.bazel.sdk.model.BazelBuildFileHelper; +import com.salesforce.bazel.sdk.util.BazelConstants; +import com.salesforce.bazel.sdk.util.BazelPathHelper; +import com.salesforce.bazel.sdk.util.WorkProgressMonitor; + +public class BazelPackageFinder { + LogHelper logger; + + public BazelPackageFinder() { + logger = LogHelper.log(this.getClass()); + } + + // TODO our workspace scanner is looking for Java packages, but uses primitive techniques. switch to use the aspect + // approach here, like we do with the classpath computation. + + public void findBuildFileLocations(File dir, WorkProgressMonitor monitor, Set buildFileLocations, int depth) { + if (!dir.isDirectory()) { + return; + } + + try { + File[] dirFiles = dir.listFiles(); + for (File dirFile : dirFiles) { + + if (shouldIgnore(dirFile, depth)) { + continue; + } + + if (isBuildFile(dirFile)) { + + // great, this dir is a Bazel package (but this may be a non-Java package) + // scan the BUILD file looking for java rules, only add if this is a java project + if (BazelBuildFileHelper.hasJavaRules(dirFile)) { + buildFileLocations.add(BazelPathHelper.getCanonicalFileSafely(dir)); + } + } else if (dirFile.isDirectory()) { + findBuildFileLocations(dirFile, monitor, buildFileLocations, depth + 1); + } + } + } catch (Exception anyE) { + logger.error("ERROR scanning for Bazel packages: {}", anyE.getMessage()); + } + } + + private static boolean shouldIgnore(File f, int depth) { + if (depth == 0 && f.isDirectory() && f.getName().startsWith("bazel-")) { + // this is a Bazel internal directory at the root of the project dir, ignore + // TODO should this use one of the ignore directory facilities at the bottom of this class? + return true; + } + return false; + } + + private static boolean isBuildFile(File candidate) { + return BazelConstants.BUILD_FILE_NAMES.contains(candidate.getName()); + } +} diff --git a/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/workspace/BazelWorkspaceScanner.java b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/workspace/BazelWorkspaceScanner.java new file mode 100644 index 0000000..187037e --- /dev/null +++ b/com.salesforce.b2eclipse.jdt.ls/src/main/java/com/salesforce/bazel/sdk/workspace/BazelWorkspaceScanner.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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.salesforce.bazel.sdk.workspace; + +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.TreeSet; + +import com.salesforce.bazel.sdk.model.BazelPackageInfo; + +/** + * Scans a Bazel workspace looking for Java packages (BUILD files that have java_binary or java_library targets). It is + * assumed that the user will provide the root workspace directory (where the WORKSPACE file is) and we will scan the + * subtree below that. + */ +public class BazelWorkspaceScanner { + + public static String getBazelWorkspaceName(String bazelWorkspaceRootDirectory) { + // TODO pull the workspace name out of the WORKSPACE file, until then use the directory name (e.g. bazel-demo) + String bazelWorkspaceName = "workspace"; + if (bazelWorkspaceRootDirectory != null) { + int lastSlash = bazelWorkspaceRootDirectory.lastIndexOf(File.separator); + if (lastSlash >= 0 && (bazelWorkspaceRootDirectory.length() - lastSlash) > 3) { + // add the directory name to the label, if it is meaningful (>3 chars) + bazelWorkspaceName = bazelWorkspaceRootDirectory.substring(lastSlash + 1); + } else { + bazelWorkspaceName = bazelWorkspaceRootDirectory; + } + } + return bazelWorkspaceName; + } + + /** + * Get a list of candidate Bazel packages to import. This list is provided to the user in the form of a tree + * control. + *

+ * Currently, the list returned will always be of size 1. It represents the root node of the scanned Bazel + * workspace. The root node has child node references, and the tree expands from there. + *

+ * TODO support scanning at an arbitrary location inside of a Bazel workspace (e.g. //projects/libs) and have the + * scanner crawl up to the WORKSPACE root from there. + * + * @param rootDirectory + * the directory to scan, which must be the root node of a Bazel workspace + * @return the workspace root BazelPackageInfo + */ + public BazelPackageInfo getPackages(String rootDirectory) throws IOException { + if (rootDirectory == null || rootDirectory.isEmpty()) { + // this is the initialization state of the wizard + return null; + } + File workspaceRootDir = new File(rootDirectory); + try { + workspaceRootDir = workspaceRootDir.getCanonicalFile(); + } catch (IOException ioe) { + ioe.printStackTrace(); + return null; + } + return getPackages(workspaceRootDir); + } + + /** + * Get a list of candidate Bazel packages to import. This list is provided to the user in the form of a tree + * control. + *

+ * Currently, the list returned will always be of size 1. It represents the root node of the scanned Bazel + * workspace. The root node has child node references, and the tree expands from there. + *

+ * TODO support scanning at an arbitrary location inside of a Bazel workspace (e.g. //projects/libs) and have the + * scanner crawl up to the WORKSPACE root from there. + * + * @param rootDirectory + * the directory to scan, which must be the root node of a Bazel workspace + * @return the workspace root BazelPackageInfo + */ + public BazelPackageInfo getPackages(File rootDirectoryFile) throws IOException { + if (rootDirectoryFile == null || !rootDirectoryFile.exists() || !rootDirectoryFile.isDirectory()) { + // this is the initialization state of the wizard + return null; + } + String rootDirectory = rootDirectoryFile.getCanonicalPath(); + BazelPackageInfo workspace = new BazelPackageInfo(rootDirectoryFile); + + // TODO the correct way to do this is put the scan on another thread, and allow it to update the progress monitor. + // Do it on-thread for now as it is easiest. + + Set projects = new TreeSet<>(); + BazelPackageFinder packageFinder = new BazelPackageFinder(); + packageFinder.findBuildFileLocations(rootDirectoryFile, null, projects, 0); + + int sizeOfWorkspacePath = rootDirectory.length(); + for (File project : projects) { + String projectPath = project.getCanonicalPath(); + + if (projectPath.equals(rootDirectory)) { + // root path, already created the root node + continue; + } + + // TODO ooh, this bazel package path manipulation seems error prone + String relativePath = projectPath.substring(sizeOfWorkspacePath + 1); + + // instantiate the project info object, which will automatically hook itself to the appropriate parents + new BazelPackageInfo(workspace, relativePath); + } + + return workspace; + } + +}