Skip to content

Commit

Permalink
GH-438: Extract source and import resolution to a new class
Browse files Browse the repository at this point in the history
  • Loading branch information
ascopes committed Nov 9, 2024
1 parent 9cc3fe6 commit 3f75cd2
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@

package io.github.ascopes.protobufmavenplugin.generation;

import io.github.ascopes.protobufmavenplugin.dependencies.MavenArtifactPathResolver;
import io.github.ascopes.protobufmavenplugin.dependencies.ResolutionException;
import io.github.ascopes.protobufmavenplugin.plugins.BinaryPluginResolver;
import io.github.ascopes.protobufmavenplugin.plugins.JvmPluginResolver;
import io.github.ascopes.protobufmavenplugin.plugins.ResolvedProtocPlugin;
import io.github.ascopes.protobufmavenplugin.protoc.ArgLineBuilder;
import io.github.ascopes.protobufmavenplugin.protoc.CommandLineExecutor;
import io.github.ascopes.protobufmavenplugin.protoc.ProtocResolver;
import io.github.ascopes.protobufmavenplugin.sources.SourceGlobFilter;
import io.github.ascopes.protobufmavenplugin.sources.ProjectInputResolver;
import io.github.ascopes.protobufmavenplugin.sources.SourceListing;
import io.github.ascopes.protobufmavenplugin.sources.SourceResolver;
import io.github.ascopes.protobufmavenplugin.utils.FileUtils;
import java.io.IOException;
import java.nio.file.Files;
Expand Down Expand Up @@ -57,30 +55,27 @@ public final class ProtobufBuildOrchestrator {
private static final Logger log = LoggerFactory.getLogger(ProtobufBuildOrchestrator.class);

private final MavenSession mavenSession;
private final MavenArtifactPathResolver artifactPathResolver;
private final ProtocResolver protocResolver;
private final BinaryPluginResolver binaryPluginResolver;
private final JvmPluginResolver jvmPluginResolver;
private final SourceResolver protoListingResolver;
private final CommandLineExecutor commandLineExecutor;
private final ProjectInputResolver projectInputResolver;

@Inject
public ProtobufBuildOrchestrator(
MavenSession mavenSession,
MavenArtifactPathResolver artifactPathResolver,
ProtocResolver protocResolver,
BinaryPluginResolver binaryPluginResolver,
JvmPluginResolver jvmPluginResolver,
SourceResolver protoListingResolver,
CommandLineExecutor commandLineExecutor
CommandLineExecutor commandLineExecutor,
ProjectInputResolver projectInputResolver
) {
this.mavenSession = mavenSession;
this.artifactPathResolver = artifactPathResolver;
this.protocResolver = protocResolver;
this.binaryPluginResolver = binaryPluginResolver;
this.jvmPluginResolver = jvmPluginResolver;
this.protoListingResolver = protoListingResolver;
this.commandLineExecutor = commandLineExecutor;
this.projectInputResolver = projectInputResolver;
}

public boolean generate(GenerationRequest request) throws ResolutionException, IOException {
Expand All @@ -89,10 +84,9 @@ public boolean generate(GenerationRequest request) throws ResolutionException, I
final var protocPath = discoverProtocPath(request);

final var resolvedPlugins = discoverPlugins(request);
final var sourcePaths = discoverCompilableSources(request);
final var importPaths = discoverImportPaths(sourcePaths, request);
final var projectInputs = projectInputResolver.resolveProjectInputs(request);

if (sourcePaths.isEmpty()) {
if (projectInputs.getCompilableSources().isEmpty()) {
if (request.isFailOnMissingSources()) {
log.error("No protobuf sources found. If this is unexpected, check your "
+ "configuration and try again.");
Expand All @@ -119,7 +113,7 @@ public boolean generate(GenerationRequest request) throws ResolutionException, I
var argLineBuilder = new ArgLineBuilder(protocPath)
.fatalWarnings(request.isFatalWarnings())
.importPaths(
importPaths.stream()
projectInputs.getImports().stream()
.map(SourceListing::getSourceRoot)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
Expand All @@ -134,7 +128,7 @@ public boolean generate(GenerationRequest request) throws ResolutionException, I
// GH-269: Add the plugins after the enabled languages to support generated code injection
argLineBuilder.plugins(resolvedPlugins, request.getOutputDirectory());

var sourceFiles = sourcePaths
var sourceFiles = projectInputs.getCompilableSources()
.stream()
.map(SourceListing::getSourceProtoFiles)
.flatMap(Collection::stream)
Expand All @@ -149,7 +143,10 @@ public boolean generate(GenerationRequest request) throws ResolutionException, I
registerSourceRoots(request);

if (request.isEmbedSourcesInClassOutputs()) {
embedSourcesInClassOutputs(request.getSourceRootRegistrar(), sourcePaths);
embedSourcesInClassOutputs(
request.getSourceRootRegistrar(),
projectInputs.getCompilableSources()
);
}

return true;
Expand Down Expand Up @@ -181,73 +178,6 @@ private Collection<ResolvedProtocPlugin> discoverPlugins(
.collect(Collectors.toUnmodifiableList());
}

private Collection<SourceListing> discoverImportPaths(
Collection<SourceListing> sourcePathListings,
GenerationRequest request
) throws ResolutionException {
var artifactPaths = artifactPathResolver.resolveDependencies(
request.getImportDependencies(),
request.getDependencyResolutionDepth(),
request.getDependencyScopes(),
!request.isIgnoreProjectDependencies(),
request.isFailOnInvalidDependencies()
);

var filter = new SourceGlobFilter();

var importPathListings = protoListingResolver.createProtoFileListings(
concat(request.getImportPaths(), artifactPaths),
filter
);

// Use the source paths here as well and use them first to give them precedence. This works
// around GH-172 where we can end up with different versions on the import and source paths
// depending on how dependency conflicts arise.
return Stream.concat(sourcePathListings.stream(), importPathListings.stream())
.distinct()
.collect(Collectors.toUnmodifiableList());
}

private Collection<SourceListing> discoverCompilableSources(
GenerationRequest request
) throws ResolutionException {
log.debug("Discovering all compilable protobuf source files");

var filter = new SourceGlobFilter(request.getIncludes(), request.getExcludes());

var sourcePathsListings = protoListingResolver.createProtoFileListings(
request.getSourceRoots(),
filter
);

var sourceDependencies = artifactPathResolver.resolveDependencies(
request.getSourceDependencies(),
request.getDependencyResolutionDepth(),
request.getDependencyScopes(),
false,
request.isFailOnInvalidDependencies()
);

var sourceDependencyListings = protoListingResolver.createProtoFileListings(
sourceDependencies,
filter
);

var sourcePaths = concat(sourcePathsListings, sourceDependencyListings);

var sourceFileCount = sourcePaths.stream()
.mapToInt(sourcePath -> sourcePath.getSourceProtoFiles().size())
.sum();

log.info(
"Generating source code for {} from {}",
pluralize(sourceFileCount, "protobuf file"),
pluralize(sourcePaths.size(), "source file tree")
);

return sourcePaths;
}

private void createOutputDirectories(GenerationRequest request) throws IOException {
var directory = request.getOutputDirectory();
log.debug("Creating {}", directory);
Expand Down Expand Up @@ -302,17 +232,12 @@ private void embedSourcesInClassOutputs(
}
}

// TODO(ascopes): move this into utility class and deduplicate across the project.
@SafeVarargs
@SuppressWarnings("varargs")
private static <T> List<T> concat(Collection<? extends T>... collections) {
return Stream.of(collections)
.flatMap(Collection::stream)
.collect(Collectors.toUnmodifiableList());
}

private static String pluralize(int count, String name) {
return count == 1
? "1 " + name
: count + " " + name + "s";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 - 2024, Ashley Scopes.
*
* 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 io.github.ascopes.protobufmavenplugin.sources;

import java.util.Collection;
import org.immutables.value.Value.Immutable;

/**
* Wrapper around a collection of source and import listings.
*
* @author Ashley Scopes
* @since 2.7.0
*/
@Immutable
public interface ProjectInputListing {
Collection<SourceListing> getCompilableSources();

Collection<SourceListing> getImports();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright (C) 2023 - 2024, Ashley Scopes.
*
* 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 io.github.ascopes.protobufmavenplugin.sources;

import io.github.ascopes.protobufmavenplugin.dependencies.MavenArtifactPathResolver;
import io.github.ascopes.protobufmavenplugin.dependencies.ResolutionException;
import io.github.ascopes.protobufmavenplugin.generation.GenerationRequest;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Component that resolves all protobuf source and import paths for a given plugin invocation,
* ensuring they are in a location that is accessible by the {@code protoc} binary.
*
* @author Ashley Scopes
* @since 2.7.0
*/
@Named
public final class ProjectInputResolver {
private static final Logger log = LoggerFactory.getLogger(ProjectInputResolver.class);

private final MavenArtifactPathResolver artifactPathResolver;
private final SourceResolver sourceResolver;

@Inject
public ProjectInputResolver(
MavenArtifactPathResolver artifactPathResolver,
SourceResolver sourceResolver
) {
this.artifactPathResolver = artifactPathResolver;
this.sourceResolver = sourceResolver;
}

public ProjectInputListing resolveProjectInputs(
GenerationRequest request
) throws ResolutionException {
var compilableSources = resolveCompilableSources(request);
var imports = resolveImports(request, compilableSources);

return ImmutableProjectInputListing.builder()
.compilableSources(compilableSources)
.imports(imports)
.build();
}

private Collection<SourceListing> resolveCompilableSources(
GenerationRequest request
) throws ResolutionException {
log.debug("Discovering all compilable protobuf source files");

var filter = new SourceGlobFilter(request.getIncludes(), request.getExcludes());

var sourcePathsListings = sourceResolver.resolveSources(
request.getSourceRoots(),
filter
);

var sourceDependencies = artifactPathResolver.resolveDependencies(
request.getSourceDependencies(),
request.getDependencyResolutionDepth(),
request.getDependencyScopes(),
false,
request.isFailOnInvalidDependencies()
);

var sourceDependencyListings = sourceResolver.resolveSources(
sourceDependencies,
filter
);

var sourcePaths = concat(sourcePathsListings, sourceDependencyListings);

var sourceFileCount = sourcePaths.stream()
.mapToInt(sourcePath -> sourcePath.getSourceProtoFiles().size())
.sum();

log.info(
"Generating source code for {} from {}",
pluralize(sourceFileCount, "protobuf file"),
pluralize(sourcePaths.size(), "source file tree")
);

return sourcePaths;
}

private Collection<SourceListing> resolveImports(
GenerationRequest request,
Collection<SourceListing> knownSourceListings
) throws ResolutionException {
var artifactPaths = artifactPathResolver.resolveDependencies(
request.getImportDependencies(),
request.getDependencyResolutionDepth(),
request.getDependencyScopes(),
!request.isIgnoreProjectDependencies(),
request.isFailOnInvalidDependencies()
);

var filter = new SourceGlobFilter();

var importListings = sourceResolver.resolveSources(
concat(request.getImportPaths(), artifactPaths),
filter
);

// Use the source paths here as well and use them first to give them precedence. This works
// around GH-172 where we can end up with different versions on the import and source paths
// depending on how dependency conflicts arise.
return Stream.concat(knownSourceListings.stream(), importListings.stream())
.distinct()
.collect(Collectors.toUnmodifiableList());
}

@SafeVarargs
@SuppressWarnings("varargs")
private static <T> List<T> concat(Collection<? extends T>... collections) {
return Stream.of(collections)
.flatMap(Collection::stream)
.collect(Collectors.toUnmodifiableList());
}

private static String pluralize(int count, String name) {
return count == 1
? "1 " + name
: count + " " + name + "s";
}
}
Loading

0 comments on commit 3f75cd2

Please sign in to comment.