Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SyncListener for aspect templating #6871

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions base/src/META-INF/blaze-base.xml
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@
<SyncListener implementation="com.google.idea.blaze.base.command.info.BlazeInfoProvider$Invalidator"/>
<SyncListener implementation="com.google.idea.blaze.base.model.ExternalWorkspaceDataProvider$Invalidator"/>
<SyncListener implementation="com.google.idea.blaze.base.qsync.QuerySyncAsyncFileListener$QuerySyncListener"/>
<SyncListener implementation="com.google.idea.blaze.base.sync.aspects.strategy.SyncAspectTemplateProvider"/>
<SyncPlugin implementation="com.google.idea.blaze.base.lang.buildfile.sync.BuildLangSyncPlugin"/>
<SyncPlugin implementation="com.google.idea.blaze.base.sync.libraries.ExternalLibraryManager$SyncPlugin"/>
<BuildFlagsProvider implementation="com.google.idea.blaze.base.command.BuildFlagsProviderImpl"/>
Expand Down
39 changes: 0 additions & 39 deletions base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ public class BlazeSyncManager {

private final Project project;
private static final Logger logger = Logger.getInstance(BlazeSyncManager.class);
private final Map<LanguageClass, String> supportedLanguageAspectTemplate = Map.of(
LanguageClass.JAVA, "java_info.template.bzl",
LanguageClass.GENERIC, "java_info.template.bzl"
);

public BlazeSyncManager(Project project) {
this.project = project;
Expand All @@ -104,14 +100,6 @@ public void requestProjectSync(BlazeSyncParams syncParams) {
}
SaveUtil.saveAllFiles();

try {
AspectRepositoryProvider.copyAspectTemplatesIfNotExists(project);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}

prepareProjectAspect();

BlazeImportSettings importSettings =
BlazeImportSettingsManager.getInstance(project).getImportSettings();
if (importSettings == null) {
Expand Down Expand Up @@ -193,33 +181,6 @@ public void requestProjectSync(BlazeSyncParams syncParams) {
});
}

private void prepareProjectAspect() {
var manager =
BlazeProjectDataManager.getInstance(project);

if (manager == null) return;

var projectData = manager.getBlazeProjectData();
if (projectData == null) return;
var optionalAspectTemplateDir = AspectRepositoryProvider.getProjectAspectDirectory(project);
if (optionalAspectTemplateDir.isEmpty()) return;
var aspectTemplateDir = optionalAspectTemplateDir.get().toPath();
var templateWriter = new TemplateWriter(aspectTemplateDir);
var activeLanguages = projectData.getWorkspaceLanguageSettings().getActiveLanguages();
var supportedLanguages = activeLanguages.stream().filter(supportedLanguageAspectTemplate::containsKey);
var isAtLeastBazel8 = projectData.getBlazeVersionData().bazelIsAtLeastVersion(8, 0, 0);
var templateVariableMap = Map.of(
"bazel8OrAbove", isAtLeastBazel8 ? "true" : "false",
"isJavaEnabled", activeLanguages.contains(LanguageClass.JAVA) || activeLanguages.contains(LanguageClass.GENERIC) ? "true" : "false"
);
supportedLanguages.forEach(language -> {
var templateFileName = supportedLanguageAspectTemplate.get(language);
var realizedFileName = templateFileName.replace(".template.bzl", ".bzl");
var realizedFile = aspectTemplateDir.resolve(realizedFileName);
templateWriter.writeToFile(templateFileName, realizedFile, templateVariableMap);
});
}

@VisibleForTesting
boolean shouldForceFullSync(
BlazeProjectData oldProjectData,
Expand Down
4 changes: 4 additions & 0 deletions base/src/com/google/idea/blaze/base/sync/SyncScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ private SyncScope() {}
public static class SyncFailedException extends Exception {
public SyncFailedException() {}

public SyncFailedException(String message) {
super(message);
}

public SyncFailedException(String message, Throwable cause) {
super(message, cause);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
import com.intellij.openapi.project.Project;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;

public interface AspectRepositoryProvider {
ExtensionPointName<AspectRepositoryProvider> EP_NAME =
Expand All @@ -23,9 +20,8 @@ default Optional<File> aspectTemplateDirectory() {
return Optional.empty();
}

public static Optional<File> getProjectAspectDirectory(Project project) {
String basePath = project.getBasePath();
return basePath == null ? Optional.empty() : Optional.of(Paths.get(basePath).resolve("aspect").toFile());
static Optional<File> getProjectAspectDirectory(Project project) {
return Optional.ofNullable(project.getBasePath()).map((it) -> Paths.get(it).resolve("aspect").toFile());
}

private static Optional<File> findAspectDirectory() {
Expand All @@ -36,7 +32,7 @@ private static Optional<File> findAspectDirectory() {
.orElse(Optional.empty());
}

private static Optional<File> findAspectTemplateDirectory() {
static Optional<File> findAspectTemplateDirectory() {
return EP_NAME.getExtensionsIfPointIsRegistered().stream()
.map(AspectRepositoryProvider::aspectTemplateDirectory)
.filter(Optional::isPresent)
Expand All @@ -58,46 +54,4 @@ private static Optional<String> getOverrideFlagForAspectDirectory() {
private static Optional<String> getOverrideFlagForProjectAspectDirectory(Project project) {
return getProjectAspectDirectory(project).map(it -> OVERRIDE_REPOSITORY_TEMPLATE_FLAG + "=" + it.getPath());
}

static void copyAspectTemplatesIfNotExists(Project project) throws ExecutionException {
Path destinationAspectsPath = getProjectAspectDirectory(project).map(File::toPath).orElse(null);
if (destinationAspectsPath == null) {
throw new IllegalStateException("Missing project aspect directory");
}
if (!destinationAspectsPath.toFile().exists()) {
try {
copyAspectTemplatesFromResources(destinationAspectsPath);
} catch (IOException e) {
throw new ExecutionException(e);
}
}
}

private static void copyAspectTemplatesFromResources(Path destinationPath) throws IOException {
Path aspectPath = findAspectTemplateDirectory().map(File::toPath).orElse(null);
if (aspectPath != null && Files.isDirectory(aspectPath)) {
copyFileTree(aspectPath, destinationPath);
} else {
System.out.println("Missing aspects resource");
}
}

private static void copyFileTree(Path source, Path destination) throws IOException {
Stream<Path> paths = Files.walk(source);
paths.forEach(path -> {
try {
copyUsingRelativePath(source, path, destination);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}

private static void copyUsingRelativePath(Path sourcePrefix, Path source, Path destination) throws IOException {
// only interested in bzl files that are templates
if (source.endsWith(".bzl") && !source.endsWith("template.bzl")) return;
String sourceRelativePath = sourcePrefix.relativize(source).toString();
Path destinationAbsolutePath = Paths.get(destination.toString(), sourceRelativePath);
Files.copy(source, destinationAbsolutePath);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2024 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.google.idea.blaze.base.sync.aspects.strategy;

import com.google.idea.blaze.base.model.primitives.LanguageClass;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.sync.SyncListener;
import com.google.idea.blaze.base.sync.SyncMode;
import com.google.idea.blaze.base.sync.SyncScope.SyncFailedException;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.base.util.TemplateWriter;
import com.intellij.openapi.project.Project;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;

public class SyncAspectTemplateProvider implements SyncListener {
private final Map<LanguageClass, String> supportedLanguageAspectTemplate = Map.of(
LanguageClass.JAVA, "java_info.template.bzl",
LanguageClass.GENERIC, "java_info.template.bzl"
);

@Override
public void onSyncStart(Project project, BlazeContext context, SyncMode syncMode) throws SyncFailedException {
copyProjectAspects(project);
prepareProjectAspect(project);
}

private void copyProjectAspects(Project project) throws SyncFailedException {
final var projectAspects = AspectRepositoryProvider.getProjectAspectDirectory(project).orElse(null);
if (projectAspects == null) {
throw new SyncFailedException("Missing project aspect directory");
}

// only copy project aspect once, TODO: do we need versioning here?
if (projectAspects.exists()) return;

final var templateAspects = AspectRepositoryProvider.findAspectTemplateDirectory().orElse(null);
if (templateAspects == null || !templateAspects.isDirectory()) {
throw new SyncFailedException("Missing aspect template directory");
}

try {
copyFileTree(templateAspects.toPath(), projectAspects.toPath());
} catch (IOException e) {
throw new SyncFailedException("Could not copy aspect templates", e);
}
}

private void copyFileTree(Path source, Path destination) throws IOException {
try (final var fileStream = Files.walk(source)) {
final var fileIterator = fileStream.iterator();
while (fileIterator.hasNext()) {
copyUsingRelativePath(source, fileIterator.next(), destination);
}
}
}

private void copyUsingRelativePath(Path sourcePrefix, Path source, Path destination) throws IOException {
// only interested in bzl files that are templates
if (source.endsWith(".bzl") && !source.endsWith("template.bzl")) return;

final var sourceRelativePath = sourcePrefix.relativize(source).toString();
final var destinationAbsolutePath = Paths.get(destination.toString(), sourceRelativePath);
Files.copy(source, destinationAbsolutePath);
}

private void prepareProjectAspect(Project project) throws SyncFailedException {
var manager = BlazeProjectDataManager.getInstance(project);
if (manager == null) return;

var projectData = manager.getBlazeProjectData();
if (projectData == null) return;
var optionalAspectTemplateDir = AspectRepositoryProvider.getProjectAspectDirectory(project);
if (optionalAspectTemplateDir.isEmpty()) return;
var aspectTemplateDir = optionalAspectTemplateDir.get().toPath();
var templateWriter = new TemplateWriter(aspectTemplateDir);
var activeLanguages = projectData.getWorkspaceLanguageSettings().getActiveLanguages();
var isAtLeastBazel8 = projectData.getBlazeVersionData().bazelIsAtLeastVersion(8, 0, 0);
var templateVariableMap = Map.of(
"bazel8OrAbove", isAtLeastBazel8 ? "true" : "false",
"isJavaEnabled", activeLanguages.contains(LanguageClass.JAVA) || activeLanguages.contains(LanguageClass.GENERIC) ? "true" : "false"
);

for (final var language : activeLanguages) {
var templateFileName = supportedLanguageAspectTemplate.get(language);
if (templateFileName == null) continue;

var realizedFileName = templateFileName.replace(".template.bzl", ".bzl");
var realizedFile = aspectTemplateDir.resolve(realizedFileName);

if (!templateWriter.writeToFile(templateFileName, realizedFile, templateVariableMap)) {
throw new SyncFailedException("Could not create template for: " + language);
}
}
}
}
10 changes: 7 additions & 3 deletions base/src/com/google/idea/blaze/base/util/TemplateWriter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.google.idea.blaze.base.util;

import com.intellij.openapi.diagnostic.Logger;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

Expand All @@ -9,6 +10,7 @@
import java.util.Properties;

public class TemplateWriter {
private static final Logger LOG = Logger.getInstance(TemplateWriter.class);

private final Path resourcePath;
private final VelocityEngine velocityEngine;
Expand All @@ -26,7 +28,7 @@ private Properties getProperties() {
return props;
}

public void writeToFile(String templateFilePath, Path outputFile, Map<String, String> variableMap) {
public boolean writeToFile(String templateFilePath, Path outputFile, Map<String, String> variableMap) {
try {
org.apache.velocity.Template template = velocityEngine.getTemplate(templateFilePath);
VelocityContext context = new VelocityContext();
Expand All @@ -36,9 +38,11 @@ public void writeToFile(String templateFilePath, Path outputFile, Map<String, St
StringWriter writer = new StringWriter();
template.merge(context, writer);
FileUtil.writeIfDifferent(outputFile, writer.toString());

return true;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error writing template to file", e);
LOG.error("Error writing template to file", e);
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface;
import com.google.idea.blaze.base.sync.aspects.BuildResult;
import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategy.OutputGroup;
import com.google.idea.blaze.base.sync.aspects.strategy.SyncAspectTemplateProvider;
import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
Expand Down Expand Up @@ -120,6 +121,8 @@ public void doSetup() throws Throwable {
projectViewManager = new MockProjectViewManager(getProject());
ServiceHelper.registerExtension(
BlazeVcsHandlerProvider.EP_NAME, new MockBlazeVcsHandlerProvider(), thisClassDisposable);
ServiceHelper.unregisterExtension(
SyncListener.EP_NAME, SyncAspectTemplateProvider.class, thisClassDisposable);
blazeInfoData = new MockBlazeInfoRunner();
blazeModData = new MockBlazeModRunner();
blazeIdeInterface = new MockBlazeIdeInterface();
Expand Down
29 changes: 14 additions & 15 deletions clwb/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -162,23 +162,22 @@ filegroup(
visibility = ["//visibility:public"],
)

#clwb_integration_test(
# name = "simple_integration_test",
# project = "simple",
# srcs = ["tests/integrationtests/com/google/idea/blaze/clwb/SimpleTest.java"],
#)

#clwb_integration_test(
# name = "virtual_includes_integration_test",
# project = "virtual_includes",
# srcs = ["tests/integrationtests/com/google/idea/blaze/clwb/VirtualIncludesTest.java"],
#)

# TODO: fix tests
clwb_integration_test(
name = "simple_integration_test",
project = "simple",
srcs = ["tests/integrationtests/com/google/idea/blaze/clwb/SimpleTest.java"],
)

clwb_integration_test(
name = "virtual_includes_integration_test",
project = "virtual_includes",
srcs = ["tests/integrationtests/com/google/idea/blaze/clwb/VirtualIncludesTest.java"],
)

test_suite(
name = "integration_tests",
tests = [
# ":simple_integration_test",
# ":virtual_includes_integration_test",
":simple_integration_test",
":virtual_includes_integration_test",
],
)
5 changes: 4 additions & 1 deletion clwb/test_defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ def clwb_integration_test(name, project, srcs, deps = []):
srcs = srcs + native.glob(["tests/integrationtests/com/google/idea/blaze/clwb/base/*.java"]),
test_package_root = "com.google.idea.blaze.clwb",
runtime_deps = [":clwb_bazel"],
data = ["//aspect:aspect_files"],
data = [
"//aspect:aspect_files",
"//aspect_template:aspect_files",
],
jvm_flags = [
# disables the default bazel security manager, causes tests to fail on windows
"-Dcom.google.testing.junit.runner.shouldInstallTestSecurityManager=false",
Expand Down
13 changes: 13 additions & 0 deletions testing/src/com/google/idea/testing/ServiceHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ public static <T> void registerExtension(
ep.registerExtension(instance, parentDisposable);
}

public static <T> void unregisterExtension(
ExtensionPointName<T> name, Class<? extends T> clazz, Disposable parentDisposable) {
final var ep = name.getPoint();
for (final var extension : name.getExtensions()) {
if (!extension.getClass().equals(clazz)) {
continue;
}

ep.unregisterExtension(extension);
Disposer.register(parentDisposable, () -> ep.registerExtension(extension));
}
}

public static <T> void registerProjectExtension(
Project project, ExtensionPointName<T> name, T instance, Disposable parentDisposable) {
ExtensionPoint<T> ep = project.getExtensionArea().getExtensionPoint(name);
Expand Down
Loading