Skip to content

Commit

Permalink
Add compiled artifact class jars to the project proto.
Browse files Browse the repository at this point in the history
This implements support for basic java deps with the new artifact tracker, and also adds appropriate support for more types of artifact in the future:
- `ProjectProtoUpdate` is a helper class for making updates to the prooject proto, providing simpler access to the `.workspace` module and the like.
- `ArtifactDirectoryBuilder` helps to construct an `ArtifactDirectoryContents` proto message and provides paths to the final artifact desinations for adding to other parts of the project proto.
- `DependenciesProjectProtoUpdater` to hook this into the existing `ProjectProtoTransform`.i

PiperOrigin-RevId: 613549386
  • Loading branch information
Googler authored and copybara-github committed Mar 7, 2024
1 parent 8e9f9f8 commit 955b24e
Show file tree
Hide file tree
Showing 14 changed files with 500 additions and 7 deletions.
2 changes: 2 additions & 0 deletions base/src/com/google/idea/blaze/base/qsync/ProjectLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import com.google.idea.blaze.qsync.ProjectRefresher;
import com.google.idea.blaze.qsync.VcsStateDiffer;
import com.google.idea.blaze.qsync.deps.ArtifactTracker;
import com.google.idea.blaze.qsync.deps.DependenciesProjectProtoUpdater;
import com.google.idea.blaze.qsync.deps.NewArtifactTracker;
import com.google.idea.blaze.qsync.java.PackageStatementParser;
import com.google.idea.blaze.qsync.java.ParallelPackageReader;
Expand Down Expand Up @@ -154,6 +155,7 @@ public QuerySyncProject loadProject(BlazeContext context) throws BuildException
NewArtifactTracker<BlazeContext> tracker =
new NewArtifactTracker<>(
BlazeDataStorage.getProjectDataDir(importSettings).toPath(), artifactCache);
projectTransformRegistry.add(new DependenciesProjectProtoUpdater(tracker));

artifactTracker = tracker;
renderJarArtifactTracker = new RenderJarArtifactTrackerImpl();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,16 @@ public GraphToProjectConverter(
Predicate<Path> fileExistenceCheck,
Context<?> context,
ProjectDefinition projectDefinition,
ListeningExecutorService executor) {
ListeningExecutorService executor,
boolean useNewBuildArtifactLogic) {
this.packageReader = packageReader;
this.fileExistenceCheck = fileExistenceCheck;
this.context = context;
this.projectDefinition = projectDefinition;
this.executor = executor;
this.useNewResDirLogic = Suppliers.ofInstance(true);
this.guessAndroidResPackages = Suppliers.ofInstance(false);
this.useNewBuildArtifactLogic = false;
this.useNewBuildArtifactLogic = useNewBuildArtifactLogic;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.qsync.deps;

import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableCollection;
import com.google.idea.blaze.qsync.artifacts.BuildArtifact;
import com.google.idea.blaze.qsync.java.JavaArtifactInfo;
import com.google.idea.blaze.qsync.project.ProjectProto.JarDirectory;
import java.nio.file.Path;

/** Adds compiled jars from dependencies to the project. */
public class AddCompiledJavaDeps implements ProjectProtoUpdateOperation {
private final Supplier<ImmutableCollection<TargetBuildInfo>> builtTargetsSupplier;

public AddCompiledJavaDeps(Supplier<ImmutableCollection<TargetBuildInfo>> builtTargetsSupplier) {
this.builtTargetsSupplier = builtTargetsSupplier;
}

@Override
public void update(ProjectProtoUpdate update) {
ArtifactDirectoryBuilder javaDepsDir = update.artifactDirectory(Path.of("javadeps"));
for (TargetBuildInfo target : builtTargetsSupplier.get()) {
if (target.javaInfo().isPresent()) {
JavaArtifactInfo javaInfo = target.javaInfo().get();
for (BuildArtifact jar : javaInfo.jars()) {
javaDepsDir.addIfNewer(jar.path(), jar, target.buildContext());
}
}
}
if (!javaDepsDir.isEmpty()) {
update
.library(JAVA_DEPS_LIB_NAME)
.addClassesJar(
JarDirectory.newBuilder().setPath(javaDepsDir.path().toString()).setRecursive(true));
if (!update.workspaceModule().getLibraryNameList().contains(JAVA_DEPS_LIB_NAME)) {
update.workspaceModule().addLibraryName(JAVA_DEPS_LIB_NAME);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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.qsync.deps;

import static com.google.common.collect.ImmutableMap.toImmutableMap;

import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.idea.blaze.qsync.artifacts.BuildArtifact;
import com.google.idea.blaze.qsync.project.ProjectPath;
import com.google.idea.blaze.qsync.project.ProjectPath.Root;
import com.google.idea.blaze.qsync.project.ProjectProto;
import com.google.idea.blaze.qsync.project.ProjectProto.ProjectArtifact.ArtifactTransform;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;

/** Populates a {@link ProjectProto.ArtifactDirectoryContents} proto from build artifacts. */
public class ArtifactDirectoryBuilder {

@AutoValue
abstract static class Entry {

abstract String destination();

abstract BuildArtifact source();

abstract DependencyBuildContext fromBuild();

abstract ArtifactTransform transform();

static Entry create(
Path destination,
BuildArtifact source,
DependencyBuildContext fromBuild,
ArtifactTransform transform) {
return new AutoValue_ArtifactDirectoryBuilder_Entry(
destination.toString(), source, fromBuild, transform);
}

ProjectProto.ProjectArtifact toProto() {
return ProjectProto.ProjectArtifact.newBuilder()
.setBuildArtifact(
ProjectProto.BuildArtifact.newBuilder().setDigest(source().digest()).build())
.setTransform(transform())
.build();
}
}

private final Path path;
private final Map<Path, Entry> contents = Maps.newHashMap();

/**
* @param relativePath Project directory relative path of the destination directory.
*/
public ArtifactDirectoryBuilder(Path relativePath) {
Preconditions.checkState(
!relativePath.isAbsolute(), "Expected a relative path: " + relativePath);
this.path = relativePath;
}

/**
* @return Project directory relative path of the destination directory.
*/
public Path path() {
return path;
}

/**
* @return This directories root as a project path.
*/
public ProjectPath root() {
return ProjectPath.projectRelative(path());
}

/**
* Adds a new artifact to the directory is it is not already present, or was produced by a more
* recent build that an existing artifact at the same location.
*
* @param relativePath Path to place the artifact at, relative to {@link #root()}.
* @param source The artifact to put there.
* @param fromBuild The build that produced the artifact.
* @return The path to the final artifact, if it was added.
*/
@CanIgnoreReturnValue
public Optional<ProjectPath> addIfNewer(
Path relativePath, BuildArtifact source, DependencyBuildContext fromBuild) {
Entry existing = contents.get(relativePath);
if (existing != null && existing.fromBuild().startTime().isAfter(fromBuild.startTime())) {
// we already have the same artifact from a more recent build.
return Optional.empty();
}
Entry e = Entry.create(relativePath, source, fromBuild, ArtifactTransform.COPY);
contents.put(relativePath, e);
return Optional.of(ProjectPath.create(Root.PROJECT, path.resolve(relativePath)));
}

public boolean isEmpty() {
return contents.isEmpty();
}

public ProjectProto.ArtifactDirectoryContents toProto() {
return ProjectProto.ArtifactDirectoryContents.newBuilder()
.putAllContents(
contents.values().stream().collect(toImmutableMap(Entry::destination, Entry::toProto)))
.build();
}

public void addTo(ProjectProto.ArtifactDirectories.Builder directories) {
directories.putDirectories(path.toString(), toProto());
}
}
3 changes: 3 additions & 0 deletions querysync/java/com/google/idea/blaze/qsync/deps/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ java_library(
],
deps = [
":artifact_tracker_state_java_proto",
"//querysync/java/com/google/idea/blaze/qsync",
"//querysync/java/com/google/idea/blaze/qsync/artifacts",
"//querysync/java/com/google/idea/blaze/qsync/cc:cc_compilation_info_java_proto",
"//querysync/java/com/google/idea/blaze/qsync/java",
"//querysync/java/com/google/idea/blaze/qsync/java:java_target_info_java_proto",
"//querysync/java/com/google/idea/blaze/qsync/project",
"//querysync/java/com/google/idea/blaze/qsync/project:project_java_proto",
"//shared",
"//shared:artifact",
"//shared:proto",
"//shared:vcs",
"//third_party/auto_value",
"@com_google_guava_guava//jar",
"@com_google_protobuf//:protobuf_java",
"@jsr305_annotations//jar",
],
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.qsync.deps;

import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.common.Context;
import com.google.idea.blaze.exception.BuildException;
import com.google.idea.blaze.qsync.ProjectProtoTransform;
import com.google.idea.blaze.qsync.project.BuildGraphData;
import com.google.idea.blaze.qsync.project.ProjectProto.Project;

/**
* A {@link ProjectProtoTransform} that adds built artifact information to the project proto, based
* on all artifacts that have been built.
*/
public class DependenciesProjectProtoUpdater implements ProjectProtoTransform {
private final ImmutableList<ProjectProtoUpdateOperation> updateOperations;

public DependenciesProjectProtoUpdater(NewArtifactTracker<?> dependencyTracker) {
ImmutableList.Builder<ProjectProtoUpdateOperation> updateOperations =
ImmutableList.<ProjectProtoUpdateOperation>builder()
.add(new AddCompiledJavaDeps(dependencyTracker::getBuiltDeps));
this.updateOperations = updateOperations.build();
}

@Override
public Project apply(Project proto, BuildGraphData graph, Context<?> context)
throws BuildException {

ProjectProtoUpdate protoUpdate = new ProjectProtoUpdate(proto);
for (ProjectProtoUpdateOperation op : updateOperations) {
op.update(protoUpdate);
}
return protoUpdate.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.qsync.deps;

import com.google.common.collect.Maps;
import com.google.idea.blaze.qsync.project.BlazeProjectDataStorage;
import com.google.idea.blaze.qsync.project.ProjectProto;
import com.google.idea.blaze.qsync.project.ProjectProto.Library;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;

/**
* Helper class for making a number of updates to the project proto.
*
* <p>This class provides a convenient way of accessing and updating various interesting parts of
* the project proto, such as the {@code .workspace} module and libraries by name.
*/
public class ProjectProtoUpdate {

private final ProjectProto.Project.Builder project;
private final ProjectProto.Module.Builder workspaceModule;
private final Map<String, Library.Builder> libraries = Maps.newHashMap();
private final Map<Path, ArtifactDirectoryBuilder> artifactDirs = Maps.newHashMap();

public ProjectProtoUpdate(ProjectProto.Project existingProject) {
this.project = existingProject.toBuilder();
this.workspaceModule = getWorkspaceModuleBuilder(project);
}

private static ProjectProto.Module.Builder getWorkspaceModuleBuilder(
ProjectProto.Project.Builder project) {
for (int i = 0; i < project.getModulesCount(); i++) {
if (project.getModules(i).getName().equals(BlazeProjectDataStorage.WORKSPACE_MODULE_NAME)) {
return project.getModulesBuilder(i);
}
}
throw new IllegalArgumentException(
"Module with name "
+ BlazeProjectDataStorage.WORKSPACE_MODULE_NAME
+ " not found in project proto.");
}

public ProjectProto.Project.Builder project() {
return project;
}

public ProjectProto.Module.Builder workspaceModule() {
return workspaceModule;
}

/** Gets a builder for a library, creating it if it doesn't already exist. */
public ProjectProto.Library.Builder library(String name) {
if (!libraries.containsKey(name)) {
Optional<ProjectProto.Library.Builder> existingProto =
project.getLibraryBuilderList().stream()
.filter(l -> l.getName().equals(name))
.findFirst();

if (existingProto.isPresent()) {
libraries.put(name, existingProto.get());
} else {
libraries.put(name, project.addLibraryBuilder().setName(name));
}
}
return libraries.get(name);
}

public ArtifactDirectoryBuilder artifactDirectory(Path relativePath) {
return artifactDirs.computeIfAbsent(relativePath, ArtifactDirectoryBuilder::new);
}

public ProjectProto.Project build() {
artifactDirs.values().forEach(d -> d.addTo(project.getArtifactDirectoriesBuilder()));
return project.build();
}
}
Loading

0 comments on commit 955b24e

Please sign in to comment.