From c1310e00edc52492fe1c6efe44b87d8cd215e640 Mon Sep 17 00:00:00 2001 From: Gayal Dassanayake Date: Wed, 4 Dec 2024 16:16:34 +0530 Subject: [PATCH 1/6] Change the resolution and the test framework --- .../IndexBasedDependencyGraphBuilder.java | 161 ++++++++++++++ .../projects/internal/ResolutionEngine.java | 110 +++++++++- .../projects/internal/index/Index.java | 63 ++++++ .../internal/index/IndexDependency.java | 59 ++++++ .../projects/internal/index/IndexPackage.java | 85 ++++++++ .../src/main/java/module-info.java | 1 + .../packages/internal/Constants.java | 1 + .../internal/PackageResolutionTestCase.java | 6 +- .../PackageResolutionTestCaseBuilder.java | 20 +- .../packages/internal/TestCaseFilePaths.java | 11 +- .../resolution/packages/internal/Utils.java | 29 +++ .../repositories/index.dot | 197 ++++++++++++++++++ 12 files changed, 739 insertions(+), 4 deletions(-) create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexDependency.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index.dot diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java new file mode 100644 index 000000000000..3533c058493e --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; + +import io.ballerina.projects.DependencyGraph; +import io.ballerina.projects.PackageDescriptor; +import io.ballerina.projects.PackageName; +import io.ballerina.projects.PackageOrg; +import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static io.ballerina.projects.DependencyResolutionType.SOURCE; +import static io.ballerina.projects.PackageDependencyScope.DEFAULT; + +// TODO: Rename this to DependencyGraphBuilder +public class IndexBasedDependencyGraphBuilder { + private final Vertex rootVertex; + private final DependencyNode rootDepNode; + private final Map vertices = new HashMap<>(); + private final Map> depGraph = new HashMap<>(); + + public IndexBasedDependencyGraphBuilder(PackageDescriptor root) { + rootVertex = new Vertex(root.org(), root.name()); + rootDepNode = new DependencyNode(root, DEFAULT, SOURCE); + vertices.put(rootVertex, rootDepNode); + depGraph.put(rootVertex, new HashSet<>()); + } + + public void addDirectDependency(DependencyNode targetNode) { + PackageDescriptor target = targetNode.pkgDesc(); + Vertex targetVertex = new Vertex(target.org(), target.name()); + vertices.put(targetVertex, targetNode); + depGraph.get(rootVertex).add(targetVertex); + depGraph.put(targetVertex, new HashSet<>()); + } + + // TODO: streamline the below logic + public void addDependency(DependencyNode sourceNode, DependencyNode targetNode) { + PackageDescriptor source = sourceNode.pkgDesc(); + PackageDescriptor target = targetNode.pkgDesc(); + Vertex sourceVertex = new Vertex(source.org(), source.name()); + Vertex targetVertex = new Vertex(target.org(), target.name()); + + DependencyNode existingSourceNode = vertices.get(sourceVertex); + if (existingSourceNode != null && existingSourceNode.scope().equals(DEFAULT)) { + sourceNode = new DependencyNode(source, DEFAULT, sourceNode.resolutionType()); + } + DependencyNode existingTargetNode = vertices.get(targetVertex); + if (existingTargetNode != null && existingTargetNode.scope().equals(DEFAULT)) { + targetNode = new DependencyNode(target, DEFAULT, targetNode.resolutionType()); + } + + vertices.put(sourceVertex, sourceNode); + vertices.put(targetVertex, targetNode); + + // if the source node is not in the vertices list, we need to add that to the graph, or + // if the source node version is getting updated, the list of dependencies should be reset and re-added, + // we reset the list of dependencies. + if (existingSourceNode == null || !existingSourceNode.equals(sourceNode)) { + depGraph.put(sourceVertex, new HashSet<>()); + } + if (!depGraph.containsKey(targetVertex)) { + depGraph.put(targetVertex, new HashSet<>()); + } + depGraph.get(sourceVertex).add(targetVertex); + } + + public DependencyGraph buildGraph() { + removeDanglingNodes(); + DependencyGraph.DependencyGraphBuilder graphBuilder = DependencyGraph.DependencyGraphBuilder.getBuilder(rootDepNode); + for (Map.Entry> dependencyMapEntry : depGraph.entrySet()) { + Vertex graphNodeKey = dependencyMapEntry.getKey(); + Set graphNodeValues = dependencyMapEntry.getValue(); + + DependencyNode pkgDescKey = vertices.get(graphNodeKey); + Set pkgDescValues; + if (graphNodeValues.isEmpty()) { + pkgDescValues = Collections.emptySet(); + } else { + pkgDescValues = new HashSet<>(graphNodeValues.size()); + for (Vertex vertex : graphNodeValues) { + pkgDescValues.add(vertices.get(vertex)); + } + } + graphBuilder.addDependencies(pkgDescKey, pkgDescValues); + } + return graphBuilder.build(); + } + + private void removeDanglingNodes() { + Set danglingVertices = new HashSet<>(vertices.keySet()); + removeDanglingNodes(rootVertex, danglingVertices); + + for (Vertex danglingVertex : danglingVertices) { + vertices.remove(danglingVertex); + depGraph.remove(danglingVertex); + vertices.remove(danglingVertex); + } + } + + private void removeDanglingNodes(Vertex nodeVertex, Set danglingVertices) { + danglingVertices.remove(nodeVertex); + Set dependencies = depGraph.get(nodeVertex); + for (Vertex dep : dependencies) { + if (!danglingVertices.contains(dep)) { + continue; + } + removeDanglingNodes(dep, danglingVertices); + } + } + + public void printGraph() { + System.out.println("Dependency Graph:"); + for (Map.Entry> entry : depGraph.entrySet()) { + System.out.println(entry.getKey() + " -> " + entry.getValue()); + } + } + + /** + * Represents a Vertex in the DAG maintained by the PackageDependencyGraphBuilder. + * + * @since 2.0.0 + */ + private record Vertex(PackageOrg org, PackageName name) { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + Vertex vertex = (Vertex) o; + return org.equals(vertex.org) && name.equals(vertex.name); + } + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java index 73713023da59..1d34ec6739a6 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java @@ -23,6 +23,8 @@ import io.ballerina.projects.PackageDependencyScope; import io.ballerina.projects.PackageDescriptor; import io.ballerina.projects.PackageVersion; +import io.ballerina.projects.ProjectException; +import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.SemanticVersion.VersionCompatibilityResult; import io.ballerina.projects.environment.ModuleLoadRequest; import io.ballerina.projects.environment.PackageLockingMode; @@ -32,18 +34,24 @@ import io.ballerina.projects.environment.ResolutionRequest; import io.ballerina.projects.environment.ResolutionResponse; import io.ballerina.projects.internal.PackageDependencyGraphBuilder.NodeStatus; +import io.ballerina.projects.internal.index.Index; +import io.ballerina.projects.internal.index.IndexDependency; +import io.ballerina.projects.internal.index.IndexPackage; import io.ballerina.projects.util.ProjectConstants; import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticInfo; import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import org.wso2.ballerinalang.util.RepoUtils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Queue; import java.util.Set; /** @@ -62,12 +70,16 @@ public class ResolutionEngine { private String dependencyGraphDump; private DiagnosticResult diagnosticResult; private Set unresolvedDeps = null; + private Index index; + private boolean indexTest; public ResolutionEngine(PackageDescriptor rootPkgDesc, BlendedManifest blendedManifest, PackageResolver packageResolver, ModuleResolver moduleResolver, - ResolutionOptions resolutionOptions) { + ResolutionOptions resolutionOptions, + Index index, + boolean indexTest) { this.rootPkgDesc = rootPkgDesc; this.blendedManifest = blendedManifest; this.packageResolver = packageResolver; @@ -77,6 +89,13 @@ public ResolutionEngine(PackageDescriptor rootPkgDesc, this.graphBuilder = new PackageDependencyGraphBuilder(rootPkgDesc, resolutionOptions); this.diagnostics = new ArrayList<>(); this.dependencyGraphDump = ""; + this.index = index; + this.indexTest = indexTest; + } + + // TODO: remove this and have the logic in the constructor once this is finalized. + public void populateIndex(List packageDescriptors) { + index.putPackages(packageDescriptors); } public DiagnosticResult diagnosticResult() { @@ -90,6 +109,9 @@ public DependencyGraph resolveDependencies(Collection directDependencies = resolvePackages(moduleLoadRequests); + if (indexTest) { + return resolveDependenciesWithIndex(directDependencies); + } // 2) Create the static/initial dependency graph. // This graph contains direct dependencies and their transitives, // but we don't update versions. @@ -108,6 +130,92 @@ public DependencyGraph resolveDependencies(Collection resolveDependenciesWithIndex(Collection directDependencies) { + // TODO: look at how to handle the blended manifest versions + IndexBasedDependencyGraphBuilder graphBuilder = new IndexBasedDependencyGraphBuilder(rootPkgDesc); + for (DependencyNode directDependency : directDependencies) { + graphBuilder.addDirectDependency(directDependency); + } + Queue unresolvedNodes = new LinkedList<>(directDependencies); + while (!unresolvedNodes.isEmpty()) { + DependencyNode pkgNode = unresolvedNodes.remove(); + PackageDescriptor pkg = pkgNode.pkgDesc(); + List indexPackageVersions = index.getPackage(pkg.org(), pkg.name()); + if (indexPackageVersions == null || indexPackageVersions.isEmpty()) { + throw new ProjectException("Package not found in the index: " + pkg); + } + // TODO: make locking mode, a part of the node itself. + Optional manifestPkg = blendedManifest.dependency(pkg.org(), pkg.name()); + IndexPackage selectedPackage = getLatestCompatibleIndexVersion(indexPackageVersions, pkg, manifestPkg, graphBuilder); + DependencyNode updatedPkgNode = new DependencyNode(selectedPackage.descriptor(), pkgNode.scope(), + pkgNode.resolutionType()); + for (IndexDependency dep : selectedPackage.dependencies()) { + PackageDescriptor depDesc = PackageDescriptor.from(dep.org(), dep.name(), dep.version()); + // TODO: handle different resolution types + // TODO: the scope of the dependency is currently not recorded in the index. Can we safely do that? + // The testOnly scoped packages won't be needed by any transitive dependencies. + DependencyNode depNode = new DependencyNode(depDesc, pkgNode.scope(), pkgNode.resolutionType()); + unresolvedNodes.add(depNode); + graphBuilder.addDependency(updatedPkgNode, depNode); + } + } + return graphBuilder.buildGraph(); + } + + // TODO: refactor and make this method pretty + // Consider the repositories, scope etc here. + private IndexPackage getLatestCompatibleIndexVersion(List indexPackageVersions, + PackageDescriptor recordedPkg, + Optional manifestPkg, + IndexBasedDependencyGraphBuilder graph) { + // TODO: use the value in the graph as well + SemanticVersion currentBallerinaVersion = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()); + + Optional latest = Optional.empty(); + + // compare with the previously fetched value from the index + if (recordedPkg.version() != null) { + Optional latestOpt = index.getVersion(recordedPkg.org(), recordedPkg.name(), recordedPkg.version()); + if (latestOpt.isEmpty()) { + throw new ProjectException("Package not found in the index: " + recordedPkg); + } + latest = latestOpt; + } + + // compare with the version recorded in the blended manifest + if (manifestPkg.isPresent()) { + BlendedManifest.Dependency dep = manifestPkg.get(); + Optional latestOpt = index.getVersion(dep.org(), dep.name(), dep.version()); + if (latestOpt.isEmpty()) { + throw new ProjectException("Package not found in the index: " + dep.org() + "/" + dep.name() + ":" + dep.version()); + } + if (latest.isEmpty()) { + latest = latestOpt; + } else if (latest.get().version().compareTo(latestOpt.get().version()) == VersionCompatibilityResult.LESS_THAN) { + latest = latestOpt; + } + } + + for (IndexPackage indexPackage : indexPackageVersions) { + // Distribution version check + if (currentBallerinaVersion.major() != indexPackage.ballerinaVersion().major() + || currentBallerinaVersion.minor() < indexPackage.ballerinaVersion().minor()) { + continue; + } + if (latest.isEmpty()) { + latest = Optional.of(indexPackage); + continue; + } + if (latest.get().version().compareTo(indexPackage.version()) == VersionCompatibilityResult.LESS_THAN) { + latest = Optional.of(indexPackage); + } + } + if (latest.isEmpty()) { + throw new ProjectException("No compatible version found in the index for package: " + recordedPkg); + } + return latest.get(); + } + private Collection resolvePackages(Collection moduleLoadRequests) { // Get the direct dependencies of the current package. // This list does not contain langlib and the root package. diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java new file mode 100644 index 000000000000..66b48b027541 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal.index; + +import io.ballerina.projects.PackageName; +import io.ballerina.projects.PackageOrg; +import io.ballerina.projects.PackageVersion; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +// TODO: index should merge the central index and the distribution index. +public class Index { + private final Map> packageMap; + public static final Index EMPTY_INDEX = new Index(); + + public Index() { + this.packageMap = new HashMap<>(); + } + + public Optional getVersion(PackageOrg orgName, PackageName packageName, PackageVersion version) { + List packageMap = this.packageMap.get(orgName + "/" + packageName); + if (packageMap == null) { + return Optional.empty(); + } + return packageMap.stream().filter(pkg -> pkg.descriptor().version().equals(version)).findFirst(); + } + + public void putVersion(IndexPackage pkg) { + List packageMap = this.packageMap.computeIfAbsent( + pkg.org().toString() + "/" + pkg.name().toString(), k -> new ArrayList<>()); + packageMap.add(pkg); + } + + public List getPackage(PackageOrg orgName, PackageName packageName) { + return packageMap.get(orgName + "/" + packageName); + } + + public void putPackages(List pkgs) { + for (IndexPackage descriptor : pkgs) { + putVersion(descriptor); + } + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexDependency.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexDependency.java new file mode 100644 index 000000000000..305e6c6ecbf6 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexDependency.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal.index; + +import io.ballerina.projects.PackageDescriptor; +import io.ballerina.projects.PackageName; +import io.ballerina.projects.PackageOrg; +import io.ballerina.projects.PackageVersion; + +public class IndexDependency { + private final PackageDescriptor descriptor; + + private IndexDependency(PackageDescriptor descriptor) { + this.descriptor = descriptor; + } + + public static IndexDependency from( + PackageOrg packageOrg, + PackageName packageName, + PackageVersion packageVersion) { + return new IndexDependency(PackageDescriptor.from(packageOrg, packageName, packageVersion)); + } + + public static IndexDependency from(PackageDescriptor descriptor) { + return new IndexDependency(descriptor); + } + + public PackageName name() { + return descriptor.name(); + } + + public PackageOrg org() { + return descriptor.org(); + } + + public PackageVersion version() { + return descriptor.version(); + } + + public PackageDescriptor descriptor() { + return descriptor; + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java new file mode 100644 index 000000000000..d38028bf82c0 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal.index; + +import io.ballerina.projects.PackageDescriptor; +import io.ballerina.projects.PackageName; +import io.ballerina.projects.PackageOrg; +import io.ballerina.projects.PackageVersion; +import io.ballerina.projects.SemanticVersion; + +import java.util.List; + +public class IndexPackage { + private final PackageDescriptor descriptor; + private final SemanticVersion ballerinaVersion; + private final List dependencies; + + // TODO: add other fields as necessary + private IndexPackage(PackageDescriptor descriptor, + SemanticVersion ballerinaVersion, + List dependencies) { + this.descriptor = descriptor; + this.ballerinaVersion = ballerinaVersion; + this.dependencies = dependencies; + } + + public static IndexPackage from( + PackageOrg packageOrg, + PackageName packageName, + PackageVersion packageVersion, + SemanticVersion ballerinaVersion, + List dependencies) { + return new IndexPackage( + PackageDescriptor.from(packageOrg, packageName, packageVersion), + ballerinaVersion, + dependencies); + } + + public static IndexPackage from( + PackageDescriptor descriptor, + SemanticVersion ballerinaVersion, + List dependencies) { + return new IndexPackage(descriptor, ballerinaVersion, dependencies); + } + + public PackageName name() { + return descriptor.name(); + } + + public PackageOrg org() { + return descriptor.org(); + } + + public PackageVersion version() { + return descriptor.version(); + } + + public PackageDescriptor descriptor() { + return descriptor; + } + + public List dependencies() { + return dependencies; + } + + public SemanticVersion ballerinaVersion() { + return ballerinaVersion; + } +} diff --git a/compiler/ballerina-lang/src/main/java/module-info.java b/compiler/ballerina-lang/src/main/java/module-info.java index bf773acd7296..0c3b23d84fc3 100644 --- a/compiler/ballerina-lang/src/main/java/module-info.java +++ b/compiler/ballerina-lang/src/main/java/module-info.java @@ -88,4 +88,5 @@ io.ballerina.language.server.core; exports io.ballerina.projects.plugins.completion; exports io.ballerina.projects.buildtools; + exports io.ballerina.projects.internal.index to io.ballerina.cli; } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Constants.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Constants.java index e0b9f415cda9..f42447b178d5 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Constants.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Constants.java @@ -31,6 +31,7 @@ public final class Constants { public static final String LOCAL_REPO_DIR_NAME = "local"; public static final String DEPS_TOML_FILE_NAME = "Dependencies_toml.dot"; public static final String BAL_TOML_FILE_NAME = "Ballerina_toml.dot"; + public static final String INDEX_FILE_NAME = "index.dot"; public static final String EXP_GRAPH_STICKY_FILE_NAME = "expected-graph-sticky.dot"; public static final String EXP_GRAPH_NO_STICKY_FILE_NAME = "expected-graph-nosticky.dot"; diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java index 21232ce903e6..78a9f7ca36ae 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java @@ -26,6 +26,7 @@ import io.ballerina.projects.internal.ModuleResolver; import io.ballerina.projects.internal.ResolutionEngine; import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; +import io.ballerina.projects.internal.index.Index; import java.util.Collection; @@ -40,6 +41,7 @@ public class PackageResolutionTestCase { private final BlendedManifest blendedManifest; private final PackageResolver packageResolver; private final ModuleResolver moduleResolver; + private final Index index; private final Collection moduleLoadRequests; private final DependencyGraph expectedGraphSticky; private final DependencyGraph expectedGraphNoSticky; @@ -48,6 +50,7 @@ public PackageResolutionTestCase(PackageDescriptor rootPkgDesc, BlendedManifest blendedManifest, PackageResolver packageResolver, ModuleResolver moduleResolver, + Index index, Collection moduleLoadRequests, DependencyGraph expectedGraphSticky, DependencyGraph expectedGraphNoSticky) { @@ -55,6 +58,7 @@ public PackageResolutionTestCase(PackageDescriptor rootPkgDesc, this.blendedManifest = blendedManifest; this.packageResolver = packageResolver; this.moduleResolver = moduleResolver; + this.index = index; this.moduleLoadRequests = moduleLoadRequests; this.expectedGraphSticky = expectedGraphSticky; this.expectedGraphNoSticky = expectedGraphNoSticky; @@ -63,7 +67,7 @@ public PackageResolutionTestCase(PackageDescriptor rootPkgDesc, public DependencyGraph execute(boolean sticky) { ResolutionOptions options = ResolutionOptions.builder().setOffline(true).setSticky(sticky).build(); ResolutionEngine resolutionEngine = new ResolutionEngine(rootPkgDesc, blendedManifest, - packageResolver, moduleResolver, options); + packageResolver, moduleResolver, options, index, true); return resolutionEngine.resolveDependencies(moduleLoadRequests); } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java index c37ee294934d..2b655fe4de2b 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java @@ -35,6 +35,8 @@ import io.ballerina.projects.internal.ModuleResolver; import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; import io.ballerina.projects.internal.environment.DefaultPackageResolver; +import io.ballerina.projects.internal.index.Index; +import io.ballerina.projects.internal.index.IndexPackage; import io.ballerina.projects.internal.repositories.AbstractPackageRepository; import io.ballerina.tools.diagnostics.Location; import io.ballerina.tools.text.LineRange; @@ -78,6 +80,8 @@ public static PackageResolutionTestCase build(TestCaseFilePaths filePaths, boole PackageManifest packageManifest = getPackageManifest( filePaths.ballerinaTomlPath().orElse(null), rootPkgDes); + Index index = getPackageIndex(filePaths.indexPath().orElse(null)); + // Create expected dependency graph with sticky DependencyGraph expectedGraphSticky = getPkgDescGraph( filePaths.expectedGraphStickyPath().orElse(null)); @@ -92,7 +96,7 @@ public static PackageResolutionTestCase build(TestCaseFilePaths filePaths, boole getModulesInRootPackage(rootPkgDescWrapper, rootPkgDes), blendedManifest, packageResolver, ResolutionOptions.builder().setSticky(sticky).build()); return new PackageResolutionTestCase(rootPkgDes, blendedManifest, - packageResolver, moduleResolver, moduleLoadRequests, + packageResolver, moduleResolver, index, moduleLoadRequests, expectedGraphSticky, expectedGraphNoSticky); } @@ -190,6 +194,20 @@ private static PackageManifest getPackageManifest(Path balTomlPath, PackageDescr return PackageManifest.from(rootPkgDesc, null, null, Collections.emptyMap(), dependencies); } + private static Index getPackageIndex(Path indexPath) { + if (indexPath == null) { + return Index.EMPTY_INDEX; + } + MutableGraph repoDotGraph = DotGraphUtils.createGraph(indexPath); + Index index = new Index(); + for (MutableGraph packageGraph : repoDotGraph.graphs()) { + IndexPackage indexPackage = Utils.getIndexPkgFromNode(packageGraph.name(), packageGraph.graphAttrs(), packageGraph.nodes()); + index.putVersion(indexPackage); + // TODO: Consider modules when introducing module constraint + } + return index; + } + private static PackageDescWrapper getRootPkgDescWrapper(Path appDotFilePath) { MutableGraph graph = DotGraphUtils.createGraph(appDotFilePath); PackageDescriptor pkgDesc = Utils.getPkgDescFromNode(graph.name().toString()); diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/TestCaseFilePaths.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/TestCaseFilePaths.java index 683ade1276fc..b040ca55152d 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/TestCaseFilePaths.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/TestCaseFilePaths.java @@ -34,6 +34,7 @@ public class TestCaseFilePaths { private final Path appPath; private final Path dependenciesTomlPath; private final Path ballerinaTomlPath; + private final Path indexPath; private final Path expectedGraphStickyPath; private final Path expectedGraphNoStickyPath; @@ -42,6 +43,7 @@ public class TestCaseFilePaths { Path localRepoDirPath, Path appPath, Path dependenciesTomlPath, + Path indexPath, Path ballerinaTomlPath, Path expectedGraphStickyPath, Path expectedGraphNoStickyPath) { @@ -50,6 +52,7 @@ public class TestCaseFilePaths { this.localRepoDirPath = localRepoDirPath; this.appPath = appPath; this.dependenciesTomlPath = dependenciesTomlPath; + this.indexPath = indexPath; this.ballerinaTomlPath = ballerinaTomlPath; this.expectedGraphStickyPath = expectedGraphStickyPath; this.expectedGraphNoStickyPath = expectedGraphNoStickyPath; @@ -79,6 +82,10 @@ public Optional dependenciesTomlPath() { return Optional.ofNullable(dependenciesTomlPath); } + public Optional indexPath() { + return Optional.ofNullable(indexPath); + } + public Optional expectedGraphStickyPath() { return Optional.ofNullable(expectedGraphStickyPath); } @@ -115,6 +122,8 @@ public static TestCaseFilePaths build(Path testSuitePath, Path testCasePath) { Path.of(Constants.REPO_DIR_NAME).resolve(Constants.DIST_REPO_FILE_NAME)); Path localRepoDirPath = getFilePath(testSuitePath, testCasePath, Path.of(Constants.REPO_DIR_NAME).resolve(Constants.LOCAL_REPO_DIR_NAME)); + Path indexPath = getFilePath(testSuitePath, testCasePath, + Path.of(Constants.REPO_DIR_NAME).resolve(Constants.INDEX_FILE_NAME)); Path depsTomlPath = getFilePath(testSuitePath, testCasePath, Path.of(Constants.DEPS_TOML_FILE_NAME)); Path balTomlPath = getFilePath(testSuitePath, testCasePath, Path.of(Constants.BAL_TOML_FILE_NAME)); @@ -124,7 +133,7 @@ public static TestCaseFilePaths build(Path testSuitePath, Path testCasePath) { Path.of(Constants.EXP_GRAPH_NO_STICKY_FILE_NAME)); return new TestCaseFilePaths(centralRepoPath, distRepoPath, localRepoDirPath, - appPath, depsTomlPath, balTomlPath, expGraphStickyPath, expGraphNoStickyPath); + appPath, depsTomlPath, indexPath, balTomlPath, expGraphStickyPath, expGraphNoStickyPath); } private static Path getFilePath(Path testSuitePath, Path testCasePath, Path relativeFilePath) { diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java index 217f758be360..b87d4acdf43e 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java @@ -17,6 +17,12 @@ */ package io.ballerina.projects.test.resolution.packages.internal; +import guru.nidi.graphviz.attribute.ForGraph; +import guru.nidi.graphviz.attribute.Label; +import guru.nidi.graphviz.model.Link; +import guru.nidi.graphviz.model.MutableAttributed; +import guru.nidi.graphviz.model.MutableGraph; +import guru.nidi.graphviz.model.MutableNode; import io.ballerina.projects.DependencyManifest; import io.ballerina.projects.DependencyResolutionType; import io.ballerina.projects.ModuleName; @@ -25,11 +31,16 @@ import io.ballerina.projects.PackageName; import io.ballerina.projects.PackageOrg; import io.ballerina.projects.PackageVersion; +import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.environment.ModuleLoadRequest; +import io.ballerina.projects.internal.index.IndexDependency; +import io.ballerina.projects.internal.index.IndexPackage; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Contains utility methods used throughout the test framework. @@ -78,6 +89,24 @@ public static ModuleLoadRequest getModuleLoadRequest(String name, return new ModuleLoadRequest(PackageOrg.from(split[0]), split[1], scope, resolutionType); } + public static IndexPackage getIndexPkgFromNode(Label name, MutableAttributed attrs, Collection nodes) { + PackageDescriptor descriptor = getPkgDescFromNode(name.toString()); + String balVersionStr = "2201.0.0"; + if (attrs.get("ballerinaVersion") != null) { + balVersionStr = Objects.requireNonNull(attrs.get("ballerinaVersion")).toString(); + } + SemanticVersion ballerinaVersion = SemanticVersion.from(balVersionStr); + List dependencies = new ArrayList<>(); + + for (MutableNode node: nodes) { + for (Link link : node.links()) { + String dependency = link.to().name().toString(); + dependencies.add(IndexDependency.from(getPkgDescFromNode(dependency))); + } + } + return IndexPackage.from(descriptor, ballerinaVersion, dependencies); + } + public static PackageDescriptor getPkgDescFromNode(String name, String repo) { String[] split = name.split("/"); String[] split1 = split[1].split(":"); diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index.dot new file mode 100644 index 000000000000..3ea82b262037 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index.dot @@ -0,0 +1,197 @@ +digraph "index" { + subgraph "ballerina/protobuf:0.6.0" { + "ballerinaVersion"="2201.0.0"; + } + subgraph "ballerina/protobuf:0.7.0" { + "ballerinaVersion"="2201.0.0"; + } + subgraph "ballerina/protobuf.types.timestamp:1.0.0" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "ballerina/protobuf:1.6.0" { + "ballerinaVersion"="2201.0.0"; + } + subgraph "ballerina/protobuf:1.7.0" { + "ballerinaVersion"="2201.0.0"; + "ballerina/protobuf:1.7.0" [other_modules = "protobuf.types.duration"] + } + + subgraph "ballerina/io:2.0.0-alpha.1" { + "ballerinaVersion"="2201.0.0"; + } + subgraph "ballerina/io:1.3.0-beta.1" { + "ballerinaVersion"="2201.0.0"; + } + subgraph "ballerina/io:1.1.0" { + "ballerinaVersion"="2201.0.0"; + } + subgraph "ballerina/io:1.0.2" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "samjs/foo:1.3.0" { + "ballerinaVersion"="2201.0.0"; + "samjs/foo:1.3.0" -> "samjs/bar:1.3.4" + "samjs/foo:1.3.0" -> "samjs/bazz:1.4.4" + } + + subgraph "samjs/io:1.1.0" { + "ballerinaVersion"="2201.0.0"; + } + subgraph "samjs/io:1.0.2" { + "ballerinaVersion"="2201.0.0"; + } + subgraph "samjs/io:1.0.1" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "samjs/http:1.0.0" { + "ballerinaVersion"="2201.0.0"; + "samjs/http:1.0.0" -> "samjs/io:1.0.1" + } + + subgraph "samjs/foo:1.2.1" { + "ballerinaVersion"="2201.0.0"; + "samjs/foo:1.2.1" -> "samjs/bar:1.3.4" + "samjs/foo:1.2.1" -> "samjs/bazz:1.4.4" + } + + subgraph "samjs/bazz:1.4.4" { + "ballerinaVersion"="2201.0.0"; + "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" + "samjs/bazz:1.4.4" -> "samjs/c:1.4.4" + } + + subgraph "samjs/bar:1.3.4" { + "ballerinaVersion"="2201.0.0"; + "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" + "samjs/bar:1.3.4" -> "samjs/p:1.3.4" + "samjs/bar:1.3.4" -> "samjs/q:1.4.4" + } + + subgraph "samjs/b:1.3.4" { + "ballerinaVersion"="2201.0.0"; + "samjs/b:1.3.4" -> "samjs/c:1.4.4" + } + + subgraph "samjs/p:1.3.4" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "samjs/q:1.4.4" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "samjs/c:1.4.4" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "samjs/c:1.4.5" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "myOrg/bazz:1.0.0" { + "ballerinaVersion"="2201.0.0"; + "myOrg/bazz:1.0.0" -> "myOrg/bar:1.3.1" + } + + subgraph "myOrg/bar:1.3.1" { + "ballerinaVersion"="2201.0.0"; + "myOrg/bar:1.3.1" -> "ballerinai/foo:0.1.0" + } + + subgraph "ballerinax/github:1.0.0" { + "ballerinaVersion"="2201.0.0"; + "ballerinax/github:1.0.0" -> "samjs/c:1.4.5" + } + + subgraph "ballerinax/mysql:1.0.0" { + "ballerinaVersion"="2201.0.0"; + "ballerinax/mysql:1.0.0" -> "ballerinai/transaction:1.0.1" + } + + subgraph "ballerinai/transaction:1.0.1" { + "ballerinaVersion"="2201.0.0"; + // this is added to avoid the exception thrown from the test framework + } + + subgraph "ballerinai/foo:0.1.0" { + "ballerinaVersion"="2201.0.0"; + // this is added to avoid the exception thrown from the test framework + } + + subgraph "ballerina/auth:2.1.0-alpha.1" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "ballerina/auth:2.1.0-beta.1" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "ballerina/http:1.4.0" { + "ballerinaVersion"="2201.0.0"; + "ballerina/http:1.4.0" -> "ballerina/io:1.0.2" + } + + subgraph "samjs/qux.foo:1.0.2" { + "ballerinaVersion"="2201.0.0"; + } + + subgraph "samjs/qux.foo:1.0.5" { + "ballerinaVersion"="2201.0.0"; + } + + // MOVE THIS TO THE LOCAL REPO + subgraph "samtest/io:1.5.1" { + "ballerinaVersion"="2201.0.0"; + "samtest/io:1.5.1" -> "ballerina/http:1.3.1" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + } + + // MOVE THIS TO THE DIST REPO + subgraph "ballerina/io:1.0.1" { + } + + subgraph "ballerina/io:1.2.0" { + "ballerina/io:1.2.0" -> "ballerina/cache:1.3.1" + } + + subgraph "ballerina/http:1.3.1" { + "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" + "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" + } + + subgraph "ballerina/auth:2.0.0" { + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" + } + + subgraph "ballerina/auth:2.0.1" { + "ballerina/auth:2.0.1" -> "ballerina/io:1.0.1" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.1" + } + + subgraph "ballerina/cache:1.2.1" { + } + + subgraph "ballerina/cache:1.3.1" { + } + + subgraph "ballerina/cache:1.3.2" { + } + + subgraph "ballerina/cache:1.4.0" { + } + + subgraph "ballerinai/transaction:0.0.0" { + } + subgraph "ballerinai/foo:0.0.0" { + "ballerinai/foo:0.0.0" -> "ballerina/cache:1.3.1" + } + + subgraph "ballerinax/observe:1.0.0-beta.1" { + } + subgraph "ballerinax/observe:1.0.0-alpha" { + } +} From 681f2ce9f102e70ef165b97e8a5f8cd79403d016 Mon Sep 17 00:00:00 2001 From: Gayal Dassanayake Date: Thu, 19 Dec 2024 10:20:30 +0530 Subject: [PATCH 2/6] Introduce update policies and locking modes --- .../ballerina/projects/PackageResolution.java | 12 +- .../projects/environment/LockingMode.java | 24 ++ .../environment/ResolutionOptions.java | 16 +- .../projects/environment/UpdatePolicy.java | 31 +++ .../projects/internal/BlendedManifest.java | 16 +- .../IndexBasedDependencyGraphBuilder.java | 36 ++- .../internal/LockingModeResolver.java | 109 ++++++++ .../projects/internal/ResolutionEngine.java | 233 +++++++++++++++--- .../projects/internal/UnresolvedNode.java | 24 ++ .../projects/internal/index/Index.java | 12 +- .../projects/internal/index/IndexPackage.java | 43 ++-- .../ballerina/projects/util/ProjectUtils.java | 18 ++ .../AbstractPackageResolutionTest.java | 26 +- .../packages/ExistingProjectTests.java | 114 +++++---- .../packages/internal/Constants.java | 7 +- .../internal/PackageResolutionTestCase.java | 61 +++-- .../PackageResolutionTestCaseBuilder.java | 61 +++-- .../packages/internal/TestCaseFilePaths.java | 62 +++-- .../resolution/packages/internal/Utils.java | 16 +- .../case-0001/case-description.md | 12 +- ...aph-sticky.dot => expected-graph-hard.dot} | 0 .../case-0001/expected-graph-locked.dot | 25 ++ ...nosticky.dot => expected-graph-medium.dot} | 6 +- .../case-0001/expected-graph-soft.dot | 25 ++ .../case-0002/Dependencies_toml.dot | 4 +- .../case-0002/case-description.md | 12 +- ...aph-sticky.dot => expected-graph-hard.dot} | 4 +- ...nosticky.dot => expected-graph-medium.dot} | 5 +- .../case-0002/expected-graph-soft.dot | 26 ++ .../case-0003/case-description.md | 15 +- ...aph-sticky.dot => expected-graph-hard.dot} | 0 .../case-0003/expected-graph-locked.dot | 25 ++ ...nosticky.dot => expected-graph-medium.dot} | 0 .../case-0003/expected-graph-soft.dot | 25 ++ .../case-0004/case-description.md | 15 +- ...aph-sticky.dot => expected-graph-hard.dot} | 2 +- .../case-0004/expected-graph-locked.dot | 27 ++ ...nosticky.dot => expected-graph-medium.dot} | 2 +- .../case-0004/expected-graph-soft.dot | 27 ++ .../case-0005/case-description.md | 14 +- ...aph-sticky.dot => expected-graph-hard.dot} | 2 +- ...nosticky.dot => expected-graph-medium.dot} | 0 .../case-0005/expected-graph-soft.dot | 6 + .../repositories/index-local.dot | 8 + .../repositories/index.dot | 20 +- 45 files changed, 976 insertions(+), 252 deletions(-) create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/LockingMode.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/UpdatePolicy.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/UnresolvedNode.java rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/{expected-graph-sticky.dot => expected-graph-hard.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-locked.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/{expected-graph-nosticky.dot => expected-graph-medium.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-soft.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/{expected-graph-sticky.dot => expected-graph-hard.dot} (100%) rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/{expected-graph-nosticky.dot => expected-graph-medium.dot} (95%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-soft.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/{expected-graph-sticky.dot => expected-graph-hard.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-locked.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/{expected-graph-nosticky.dot => expected-graph-medium.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-soft.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/{expected-graph-sticky.dot => expected-graph-hard.dot} (94%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-locked.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/{expected-graph-nosticky.dot => expected-graph-medium.dot} (94%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-soft.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/{expected-graph-sticky.dot => expected-graph-hard.dot} (74%) rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/{expected-graph-nosticky.dot => expected-graph-medium.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-soft.dot create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index-local.dot diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java index c59091c276ba..28901e11cc8f 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java @@ -36,6 +36,7 @@ import io.ballerina.projects.internal.ProjectDiagnosticErrorCode; import io.ballerina.projects.internal.ResolutionEngine; import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; +import io.ballerina.projects.internal.index.Index; import io.ballerina.projects.internal.repositories.CustomPkgRepositoryContainer; import io.ballerina.projects.internal.repositories.LocalPackageRepository; import io.ballerina.projects.internal.repositories.MavenPackageRepository; @@ -359,9 +360,17 @@ private DependencyGraph resolveSourceDependencies() { // 1) Get PackageLoadRequests for all the direct dependencies of this package LinkedHashSet moduleLoadRequests = getModuleLoadRequestsOfDirectDependencies(); + boolean hasDependencyManifest = rootPackageContext.dependenciesTomlContext().isPresent(); + SemanticVersion prevDistributionVersion = rootPackageContext.dependencyManifest().distributionVersion(); + boolean distributionChange = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()) + .equals(prevDistributionVersion); + boolean lessThan24HrsAfterBuild = ProjectUtils.isWithin24HoursOfLastBuild(rootPackageContext.project()); + // 2) Resolve imports to packages and create the complete dependency graph with package metadata + // TODO: find a better way to pass the new parameters. ResolutionEngine resolutionEngine = new ResolutionEngine(rootPackageContext.descriptor(), - blendedManifest, packageResolver, moduleResolver, resolutionOptions); + blendedManifest, packageResolver, moduleResolver, resolutionOptions, new Index(), + hasDependencyManifest, distributionChange, lessThan24HrsAfterBuild, false); DependencyGraph dependencyNodeGraph = resolutionEngine.resolveDependencies(moduleLoadRequests); this.dependencyGraphDump = resolutionEngine.dumpGraphs(); @@ -373,6 +382,7 @@ private DependencyGraph resolveSourceDependencies() { packageResolver); } + static Optional findModuleInPackage(PackageContext resolvedPackage, String moduleNameStr) { PackageName packageName = resolvedPackage.packageName(); ModuleName moduleName; diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/LockingMode.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/LockingMode.java new file mode 100644 index 000000000000..1434e5000396 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/LockingMode.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.environment; + +// TODO: rename this to PackageLockingMode once the transition is properly done +public enum LockingMode { + LATEST, SOFT, MEDIUM, HARD, LOCKED, INVALID +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java index 3342650b0fd2..a1199b5f8ba1 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java @@ -27,14 +27,16 @@ public class ResolutionOptions { private final boolean sticky; private final boolean dumpGraph; private final boolean dumpRawGraphs; + private final UpdatePolicy updatePolicy; private final PackageLockingMode packageLockingMode; private ResolutionOptions(boolean offline, boolean sticky, boolean dumpGraph, boolean dumpRawGraphs, - PackageLockingMode packageLockingMode) { + UpdatePolicy updatePolicy, PackageLockingMode packageLockingMode) { this.offline = offline; this.sticky = sticky; this.dumpGraph = dumpGraph; this.dumpRawGraphs = dumpRawGraphs; + this.updatePolicy = updatePolicy; this.packageLockingMode = packageLockingMode; } @@ -63,6 +65,10 @@ public boolean sticky() { return sticky; } + public UpdatePolicy updatePolicy() { + return updatePolicy; + } + public boolean dumpGraph() { return dumpGraph; } @@ -89,6 +95,7 @@ public static class ResolutionOptionBuilder { private boolean sticky = true; private boolean dumpGraph = false; private boolean dumpRawGraphs = false; + private UpdatePolicy updatePolicy = UpdatePolicy.SOFT; private PackageLockingMode packageLockingMode = PackageLockingMode.MEDIUM; public ResolutionOptionBuilder setOffline(boolean value) { @@ -101,6 +108,11 @@ public ResolutionOptionBuilder setSticky(boolean value) { return this; } + public ResolutionOptionBuilder setUpdatePolicy(UpdatePolicy value) { + updatePolicy = value; + return this; + } + public ResolutionOptionBuilder setDumpGraph(boolean value) { dumpGraph = value; return this; @@ -117,7 +129,7 @@ public ResolutionOptionBuilder setPackageLockingMode(PackageLockingMode value) { } public ResolutionOptions build() { - return new ResolutionOptions(offline, sticky, dumpGraph, dumpRawGraphs, packageLockingMode); + return new ResolutionOptions(offline, sticky, dumpGraph, dumpRawGraphs, updatePolicy, packageLockingMode); } } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/UpdatePolicy.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/UpdatePolicy.java new file mode 100644 index 000000000000..888b9bb601ba --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/UpdatePolicy.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.environment; + +/** + * Represents the dependency update policy of a package. + * + * @since 2201.12.0 + */ +public enum UpdatePolicy { + SOFT, + MEDIUM, + HARD, + LOCKED +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BlendedManifest.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BlendedManifest.java index 1ceb0d473c30..38f1a348f8f6 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BlendedManifest.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BlendedManifest.java @@ -24,6 +24,7 @@ import io.ballerina.projects.PackageName; import io.ballerina.projects.PackageOrg; import io.ballerina.projects.PackageVersion; +import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.SemanticVersion.VersionCompatibilityResult; import io.ballerina.projects.internal.repositories.AbstractPackageRepository; import io.ballerina.projects.internal.repositories.MavenPackageRepository; @@ -51,13 +52,17 @@ public class BlendedManifest { private final PackageContainer depContainer; private final DiagnosticResult diagnosticResult; + private final SemanticVersion distributionVersion; private static final Repository REPOSITORY_LOCAL = new Repository("local"); private static final Repository REPOSITORY_NOT_SPECIFIED = new Repository("not_specified"); - private BlendedManifest(PackageContainer pkgContainer, DiagnosticResult diagnosticResult) { + private BlendedManifest(PackageContainer pkgContainer, + DiagnosticResult diagnosticResult, + SemanticVersion distributionVersion) { this.depContainer = pkgContainer; this.diagnosticResult = diagnosticResult; + this.distributionVersion = distributionVersion; } public static BlendedManifest from(DependencyManifest dependencyManifest, @@ -177,7 +182,10 @@ depInPkgManifestRepo, moduleNames(depInPkgManifest, targetRepository), } } - return new BlendedManifest(depContainer, new DefaultDiagnosticResult(diagnostics)); + return new BlendedManifest( + depContainer, + new DefaultDiagnosticResult(diagnostics), + dependencyManifest.distributionVersion()); } private static DependencyRelation getRelation(boolean isTransitive) { @@ -200,6 +208,10 @@ private static Collection moduleNames(PackageManifest.Dependency depende .toList(); } + public SemanticVersion getDistributionVersion() { + return distributionVersion; + } + public Optional lockedDependency(PackageOrg org, PackageName name) { return dependency(org, name, DependencyOrigin.LOCKED); } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java index 3533c058493e..cc37f5434c12 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java @@ -55,6 +55,23 @@ public void addDirectDependency(DependencyNode targetNode) { depGraph.put(targetVertex, new HashSet<>()); } + public void addVertex(DependencyNode node) { + PackageDescriptor pkgDesc = node.pkgDesc(); + Vertex vertex = new Vertex(pkgDesc.org(), pkgDesc.name()); + DependencyNode existingNode = vertices.get(vertex); + if (existingNode != null && existingNode.scope().equals(DEFAULT)) { + node = new DependencyNode(pkgDesc, DEFAULT, node.resolutionType()); + } + vertices.put(vertex, node); + + // if the source node is not in the vertices list, we need to add that to the graph, or + // if the source node version is getting updated, the list of dependencies should be reset and re-added, + // we reset the list of dependencies. + if (existingNode == null || !existingNode.equals(node)) { + depGraph.put(vertex, new HashSet<>()); + } + } + // TODO: streamline the below logic public void addDependency(DependencyNode sourceNode, DependencyNode targetNode) { PackageDescriptor source = sourceNode.pkgDesc(); @@ -62,30 +79,23 @@ public void addDependency(DependencyNode sourceNode, DependencyNode targetNode) Vertex sourceVertex = new Vertex(source.org(), source.name()); Vertex targetVertex = new Vertex(target.org(), target.name()); - DependencyNode existingSourceNode = vertices.get(sourceVertex); - if (existingSourceNode != null && existingSourceNode.scope().equals(DEFAULT)) { - sourceNode = new DependencyNode(source, DEFAULT, sourceNode.resolutionType()); - } DependencyNode existingTargetNode = vertices.get(targetVertex); if (existingTargetNode != null && existingTargetNode.scope().equals(DEFAULT)) { targetNode = new DependencyNode(target, DEFAULT, targetNode.resolutionType()); } - - vertices.put(sourceVertex, sourceNode); vertices.put(targetVertex, targetNode); - - // if the source node is not in the vertices list, we need to add that to the graph, or - // if the source node version is getting updated, the list of dependencies should be reset and re-added, - // we reset the list of dependencies. - if (existingSourceNode == null || !existingSourceNode.equals(sourceNode)) { - depGraph.put(sourceVertex, new HashSet<>()); - } if (!depGraph.containsKey(targetVertex)) { depGraph.put(targetVertex, new HashSet<>()); } depGraph.get(sourceVertex).add(targetVertex); } + public PackageDescriptor getDependency(PackageOrg org, PackageName name) { + Vertex vertexToFetch = new Vertex(org, name); + DependencyNode node = vertices.get(vertexToFetch); + return node != null? node.pkgDesc() : null; + } + public DependencyGraph buildGraph() { removeDanglingNodes(); DependencyGraph.DependencyGraphBuilder graphBuilder = DependencyGraph.DependencyGraphBuilder.getBuilder(rootDepNode); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java new file mode 100644 index 000000000000..e2092c141c56 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; + +import io.ballerina.projects.environment.LockingMode; +import io.ballerina.projects.environment.UpdatePolicy; + +public class LockingModeResolver { + // TODO: may be we can encapsulate these fields in a new class LockingModeResolutionOptions + private final UpdatePolicy updatePolicy; + private boolean hasDependencyManifest; + private final boolean distributionChange; + private final boolean importAddition; + private final boolean lessThan24HrsAfterBuild; + + public LockingModeResolver( + UpdatePolicy updatePolicy, + boolean hasDependencyManifest, + boolean distributionChange, + boolean importAddition, + boolean lessThan24HrsAfterBuild + ) { + this.updatePolicy = updatePolicy; + this.hasDependencyManifest = hasDependencyManifest; + this.distributionChange = distributionChange; + this.importAddition = importAddition; + this.lessThan24HrsAfterBuild = lessThan24HrsAfterBuild; + } + + public LockingModes resolveLockingModes() { + if (!hasDependencyManifest) { + return resolveNoManifestLockingMode(); + } + if (distributionChange) { + return resolveDistributionChangeLockingMode(); + } + if (importAddition) { + return resolveImportAdditionLockingMode(); + } + if (lessThan24HrsAfterBuild) { + return new LockingModes(LockingMode.LOCKED); + } + return resolveDefaultLockingMode(); + } + + public record LockingModes( + LockingMode existingDirectDepMode, + LockingMode existingTransitiveDepMode, + LockingMode newDirectDepMode, + LockingMode newTransitiveDepMode) { + public LockingModes(LockingMode existingDirectDepMode, LockingMode existingTransDepMode) { + this(existingDirectDepMode, existingTransDepMode, existingDirectDepMode, existingTransDepMode); + } + public LockingModes(LockingMode mode) { + this(mode, mode, mode, mode); + } + } + + private LockingModes resolveNoManifestLockingMode() { + return switch (updatePolicy) { + case SOFT -> new LockingModes(LockingMode.LATEST, LockingMode.SOFT); + case MEDIUM -> new LockingModes(LockingMode.LATEST, LockingMode.MEDIUM); + case HARD -> new LockingModes(LockingMode.LATEST, LockingMode.HARD); + default -> new LockingModes(LockingMode.INVALID); + }; + } + + private LockingModes resolveDistributionChangeLockingMode() { + return switch (updatePolicy) { + case SOFT -> new LockingModes(LockingMode.SOFT); + case MEDIUM, HARD -> new LockingModes(LockingMode.MEDIUM); + case LOCKED -> new LockingModes(LockingMode.LOCKED); + }; + } + + private LockingModes resolveImportAdditionLockingMode() { + return switch (updatePolicy) { + case SOFT -> new LockingModes(LockingMode.SOFT, LockingMode.SOFT, LockingMode.LATEST, LockingMode.SOFT); + case MEDIUM -> new LockingModes(LockingMode.MEDIUM, LockingMode.MEDIUM, LockingMode.LATEST, LockingMode.MEDIUM); + case HARD -> new LockingModes(LockingMode.HARD, LockingMode.HARD, LockingMode.LATEST, LockingMode.HARD); + case LOCKED -> new LockingModes(LockingMode.INVALID); + }; + } + + private LockingModes resolveDefaultLockingMode() { + return switch (updatePolicy) { + case SOFT -> new LockingModes(LockingMode.SOFT); + case MEDIUM -> new LockingModes(LockingMode.MEDIUM); + case HARD -> new LockingModes(LockingMode.HARD); + default -> new LockingModes(LockingMode.LOCKED); + }; + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java index 1d34ec6739a6..0418daee7e5e 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java @@ -26,6 +26,7 @@ import io.ballerina.projects.ProjectException; import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.SemanticVersion.VersionCompatibilityResult; +import io.ballerina.projects.environment.LockingMode; import io.ballerina.projects.environment.ModuleLoadRequest; import io.ballerina.projects.environment.PackageLockingMode; import io.ballerina.projects.environment.PackageMetadataResponse; @@ -70,8 +71,11 @@ public class ResolutionEngine { private String dependencyGraphDump; private DiagnosticResult diagnosticResult; private Set unresolvedDeps = null; - private Index index; - private boolean indexTest; + private final Index index; + boolean hasDependencyManifest; + boolean distributionChange; + boolean lessThan24HrsAfterBuild; + private final boolean indexTest; public ResolutionEngine(PackageDescriptor rootPkgDesc, BlendedManifest blendedManifest, @@ -79,6 +83,9 @@ public ResolutionEngine(PackageDescriptor rootPkgDesc, ModuleResolver moduleResolver, ResolutionOptions resolutionOptions, Index index, + boolean hasDependencyManifest, + boolean distributionChange, + boolean lessThan24HrsAfterBuild, boolean indexTest) { this.rootPkgDesc = rootPkgDesc; this.blendedManifest = blendedManifest; @@ -90,6 +97,9 @@ public ResolutionEngine(PackageDescriptor rootPkgDesc, this.diagnostics = new ArrayList<>(); this.dependencyGraphDump = ""; this.index = index; + this.hasDependencyManifest = hasDependencyManifest; + this.distributionChange = distributionChange; + this.lessThan24HrsAfterBuild = lessThan24HrsAfterBuild; this.indexTest = indexTest; } @@ -131,68 +141,160 @@ public DependencyGraph resolveDependencies(Collection resolveDependenciesWithIndex(Collection directDependencies) { + LockingModeResolver.LockingModes lockingModes = getLockingModes(directDependencies); // TODO: look at how to handle the blended manifest versions IndexBasedDependencyGraphBuilder graphBuilder = new IndexBasedDependencyGraphBuilder(rootPkgDesc); + Queue unresolvedNodes = new LinkedList<>(); + initializeDirectDependencies(directDependencies, lockingModes, graphBuilder, unresolvedNodes); + processUnresolvedNodes(lockingModes, graphBuilder, unresolvedNodes); + return graphBuilder.buildGraph(); + } + + private LockingModeResolver.LockingModes getLockingModes(Collection directDependencies) { + boolean importAddition = areNewDirectDependenciesAdded(directDependencies); + LockingModeResolver lockingModeResolver = new LockingModeResolver( + resolutionOptions.updatePolicy(), + hasDependencyManifest, + distributionChange, + importAddition, + lessThan24HrsAfterBuild); + return lockingModeResolver.resolveLockingModes(); + } + + private void initializeDirectDependencies( + Collection directDependencies, + LockingModeResolver.LockingModes lockingModeMap, + IndexBasedDependencyGraphBuilder graphBuilder, + Queue unresolvedNodes) { for (DependencyNode directDependency : directDependencies) { + LockingMode directDepLockingMode = isDirectDependencyNewlyAdded(directDependency)? + lockingModeMap.newDirectDepMode() : lockingModeMap.existingDirectDepMode(); graphBuilder.addDirectDependency(directDependency); + unresolvedNodes.add(new UnresolvedNode(directDependency, directDepLockingMode)); } - Queue unresolvedNodes = new LinkedList<>(directDependencies); + } + + private void processUnresolvedNodes( + LockingModeResolver.LockingModes lockingModeMap, + IndexBasedDependencyGraphBuilder graphBuilder, + Queue unresolvedNodes) { while (!unresolvedNodes.isEmpty()) { - DependencyNode pkgNode = unresolvedNodes.remove(); + UnresolvedNode unresolvedNode = unresolvedNodes.remove(); + DependencyNode pkgNode = unresolvedNode.dependencyNode(); + LockingMode lockingMode = unresolvedNode.lockingMode(); PackageDescriptor pkg = pkgNode.pkgDesc(); List indexPackageVersions = index.getPackage(pkg.org(), pkg.name()); if (indexPackageVersions == null || indexPackageVersions.isEmpty()) { throw new ProjectException("Package not found in the index: " + pkg); } // TODO: make locking mode, a part of the node itself. - Optional manifestPkg = blendedManifest.dependency(pkg.org(), pkg.name()); - IndexPackage selectedPackage = getLatestCompatibleIndexVersion(indexPackageVersions, pkg, manifestPkg, graphBuilder); - DependencyNode updatedPkgNode = new DependencyNode(selectedPackage.descriptor(), pkgNode.scope(), + BlendedManifest.Dependency manifestPkg = blendedManifest.dependency(pkg.org(), pkg.name()).orElse(null); + IndexPackage selectedPackage = getLatestCompatibleIndexVersion( + indexPackageVersions, pkg, manifestPkg, graphBuilder, lockingMode); + DependencyNode updatedPkgNode = new DependencyNode( + PackageDescriptor.from(selectedPackage.org(), selectedPackage.name(), selectedPackage.version(), + selectedPackage.repository()), + pkgNode.scope(), pkgNode.resolutionType()); + graphBuilder.addVertex(updatedPkgNode); for (IndexDependency dep : selectedPackage.dependencies()) { - PackageDescriptor depDesc = PackageDescriptor.from(dep.org(), dep.name(), dep.version()); + + // If there is a higher version of the dependency is already in the graph, we use that. + PackageVersion depVersion = dep.version(); + PackageDescriptor currentIndexDependency = graphBuilder.getDependency(dep.org(), dep.name()); + if (currentIndexDependency != null && + currentIndexDependency.version().value().greaterThanOrEqualTo(dep.version().value())) { + depVersion = currentIndexDependency.version(); + } + PackageDescriptor depDesc = PackageDescriptor.from(dep.org(), dep.name(), depVersion); // TODO: handle different resolution types // TODO: the scope of the dependency is currently not recorded in the index. Can we safely do that? // The testOnly scoped packages won't be needed by any transitive dependencies. DependencyNode depNode = new DependencyNode(depDesc, pkgNode.scope(), pkgNode.resolutionType()); - unresolvedNodes.add(depNode); + LockingMode transitiveDepLockingMode = isNewDependency(depNode)? + lockingModeMap.newTransitiveDepMode() : lockingModeMap.existingTransitiveDepMode(); + unresolvedNodes.add(new UnresolvedNode(depNode, transitiveDepLockingMode)); graphBuilder.addDependency(updatedPkgNode, depNode); } } - return graphBuilder.buildGraph(); } // TODO: refactor and make this method pretty // Consider the repositories, scope etc here. + // Filter by deprecated status and the platform as well. private IndexPackage getLatestCompatibleIndexVersion(List indexPackageVersions, - PackageDescriptor recordedPkg, - Optional manifestPkg, - IndexBasedDependencyGraphBuilder graph) { - // TODO: use the value in the graph as well + PackageDescriptor indexRecordedPkg, + BlendedManifest.Dependency manifestRecordedPkg, + IndexBasedDependencyGraphBuilder graph, + LockingMode lockingMode) { + // If this context is restricted and invalid, we should throw an error. + if (lockingMode.equals(LockingMode.INVALID)) { + throw new ProjectException("Invalid state"); // TODO: have proper errors with the reason for the invalid state. + } + + // If the package is from the local repository, we should pick the exact version. + if (manifestRecordedPkg != null && manifestRecordedPkg.isFromLocalRepository()) { + // TODO: look at how we should handle the local repos with index. Do we merge the local ones into in memory? + return index.getVersion(manifestRecordedPkg.org(), manifestRecordedPkg.name(), manifestRecordedPkg.version(), "local") + .orElseThrow(() -> new ProjectException("Package not found in the index: " + indexRecordedPkg)); + } + + // if the locking mode is LOCKED, we return the version recorded in the manifest. + if (lockingMode.equals(LockingMode.LOCKED)) { + if (manifestRecordedPkg == null) { + throw new ProjectException("Cannot have new dependencies with the LOCKED update policy"); + } + return index.getVersion(manifestRecordedPkg.org(), manifestRecordedPkg.name(), manifestRecordedPkg.version()) + .orElseThrow(() -> new ProjectException("Package not found in the index: " + indexRecordedPkg)); + } + SemanticVersion currentBallerinaVersion = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()); - Optional latest = Optional.empty(); + Optional candidatePkg = Optional.empty(); // compare with the previously fetched value from the index - if (recordedPkg.version() != null) { - Optional latestOpt = index.getVersion(recordedPkg.org(), recordedPkg.name(), recordedPkg.version()); - if (latestOpt.isEmpty()) { - throw new ProjectException("Package not found in the index: " + recordedPkg); + if (indexRecordedPkg.version() != null) { + Optional indexPkg = index.getVersion(indexRecordedPkg.org(), indexRecordedPkg.name(), indexRecordedPkg.version()); + if (indexPkg.isEmpty()) { + throw new ProjectException("Package not found in the index: " + indexRecordedPkg); } - latest = latestOpt; + candidatePkg = indexPkg; } // compare with the version recorded in the blended manifest - if (manifestPkg.isPresent()) { - BlendedManifest.Dependency dep = manifestPkg.get(); - Optional latestOpt = index.getVersion(dep.org(), dep.name(), dep.version()); - if (latestOpt.isEmpty()) { - throw new ProjectException("Package not found in the index: " + dep.org() + "/" + dep.name() + ":" + dep.version()); + if (manifestRecordedPkg != null) { + Optional manifestIndexPkg = index.getVersion(manifestRecordedPkg.org(), manifestRecordedPkg.name(), manifestRecordedPkg.version()); + if (manifestIndexPkg.isEmpty()) { + throw new ProjectException("Package not found in the index: " + manifestRecordedPkg.org() + "/" + manifestRecordedPkg.name() + ":" + manifestRecordedPkg.version()); + } + if (candidatePkg.isEmpty()) { + candidatePkg = manifestIndexPkg; + } else if (candidatePkg.get().version() + .compareTo(manifestIndexPkg.get().version()) == VersionCompatibilityResult.INCOMPATIBLE) { + throw new ProjectException("Incompatible versions '" + + manifestIndexPkg.get().version() + "', '" + candidatePkg.get().version() + + "' found in the index for package: '" + indexRecordedPkg.org() + "/" + indexRecordedPkg.name() + "'"); + } else if (candidatePkg.get().version() + .compareTo(manifestIndexPkg.get().version()) == VersionCompatibilityResult.LESS_THAN) { + candidatePkg = manifestIndexPkg; + } + } + + PackageDescriptor graphRecordedPkg = graph.getDependency(indexRecordedPkg.org(), indexRecordedPkg.name()); + if (graphRecordedPkg != null) { + Optional graphIndexPkg = index.getVersion(graphRecordedPkg.org(), graphRecordedPkg.name(), graphRecordedPkg.version()); + if (graphIndexPkg.isEmpty()) { + throw new ProjectException("Package not found in the index: " + graphRecordedPkg.org() + "/" + graphRecordedPkg.name() + ":" + graphRecordedPkg.version()); } - if (latest.isEmpty()) { - latest = latestOpt; - } else if (latest.get().version().compareTo(latestOpt.get().version()) == VersionCompatibilityResult.LESS_THAN) { - latest = latestOpt; + if (candidatePkg.isEmpty()) { + candidatePkg = graphIndexPkg; + } else if (candidatePkg.get().version().compareTo(graphRecordedPkg.version()) == VersionCompatibilityResult.INCOMPATIBLE) { + throw new ProjectException("Incompatible versions '" + + graphRecordedPkg.version() + "', '" + candidatePkg.get().version() + + "' found in the index for package: '" + indexRecordedPkg.org() + "/" + indexRecordedPkg.name() + "'"); + } else if (candidatePkg.get().version() + .compareTo(graphRecordedPkg.version()) == VersionCompatibilityResult.LESS_THAN) { + candidatePkg = graphIndexPkg; } } @@ -202,18 +304,69 @@ private IndexPackage getLatestCompatibleIndexVersion(List indexPac || currentBallerinaVersion.minor() < indexPackage.ballerinaVersion().minor()) { continue; } - if (latest.isEmpty()) { - latest = Optional.of(indexPackage); - continue; + if (candidatePkg.isEmpty() + || isAllowedVersionBump(candidatePkg.get().version(), indexPackage.version(), lockingMode)) { + candidatePkg = Optional.of(indexPackage); } - if (latest.get().version().compareTo(indexPackage.version()) == VersionCompatibilityResult.LESS_THAN) { - latest = Optional.of(indexPackage); + } + if (candidatePkg.isEmpty()) { + throw new ProjectException("No compatible version found in the index for package: " + indexRecordedPkg); + } + return candidatePkg.get(); + } + + private boolean areNewDirectDependenciesAdded(Collection directDependencies) { + for(DependencyNode directDependency: directDependencies) { + if (isDirectDependencyNewlyAdded(directDependency)) { + return true; } } - if (latest.isEmpty()) { - throw new ProjectException("No compatible version found in the index for package: " + recordedPkg); + return false; + } + + /** + * Checks if the given direct dependency is a newly added dependency. This will return true if the + * dependency is added newly as a direct dependency or a previous transitive dependency is now a direct dependency. + * Note that the passed dependency will be considered as one of the direct dependencies without validation. + * + * @param directDependency direct dependency that needs to be checked for novelty. + * @return if the dependency is new or not + */ + private boolean isDirectDependencyNewlyAdded(DependencyNode directDependency) { + if (isNewDependency(directDependency)) { + return true; } - return latest.get(); + BlendedManifest.Dependency manifestDep = blendedManifest.dependency( + directDependency.pkgDesc().org(), directDependency.pkgDesc().name()).orElseThrow(); + return manifestDep.relation().equals(BlendedManifest.DependencyRelation.TRANSITIVE); + } + + private boolean isNewDependency(DependencyNode dependency) { + Optional manifestDep = blendedManifest.dependency( + dependency.pkgDesc().org(), dependency.pkgDesc().name()); + return manifestDep.isEmpty(); + } + + private boolean isAllowedVersionBump( + PackageVersion currentPackageVersion, + PackageVersion newPackageVersion, + LockingMode lockingMode) { + SemanticVersion currentVersion = currentPackageVersion.value(); + SemanticVersion newVersion = newPackageVersion.value(); + if (newVersion.isPreReleaseVersion()) { + return false; + } + VersionCompatibilityResult compatibility = currentVersion.compareTo(newVersion); + return switch (lockingMode) { + case LATEST -> compatibility == VersionCompatibilityResult.LESS_THAN + || newVersion.major() > currentVersion.major(); + case SOFT -> compatibility == VersionCompatibilityResult.LESS_THAN; + case MEDIUM -> currentVersion.major() == newVersion.major() + && currentVersion.minor() == newVersion.minor() + && currentVersion.patch() < newVersion.patch(); + case HARD, LOCKED -> false; + case INVALID -> false; + }; } private Collection resolvePackages(Collection moduleLoadRequests) { @@ -222,7 +375,7 @@ private Collection resolvePackages(Collection PackageContainer directDepsContainer = moduleResolver.resolveModuleLoadRequests(moduleLoadRequests); - List directDeps = new ArrayList<>(); + List directDeps = new ArrayList<>(); for (ModuleResolver.DirectPackageDependency directPkgDependency : directDepsContainer.getAll()) { PackageVersion depVersion; String repository; @@ -260,7 +413,7 @@ private Collection resolvePackages(Collection throw new IllegalStateException("Unsupported direct dependency kind: " + directPkgDependency.dependencyKind()); } - directDeps.add(new ResolutionEngine.DependencyNode( + directDeps.add(new DependencyNode( PackageDescriptor.from(depPkgDesc.org(), depPkgDesc.name(), depVersion, repository), directPkgDependency.scope(), directPkgDependency.resolutionType(), errorNode)); } @@ -720,7 +873,7 @@ public String toString() { } @Override - public int compareTo(ResolutionEngine.DependencyNode other) { + public int compareTo(DependencyNode other) { return this.pkgDesc.toString().compareTo(other.pkgDesc.toString()); } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/UnresolvedNode.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/UnresolvedNode.java new file mode 100644 index 000000000000..a3e092cda4fa --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/UnresolvedNode.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; + +import io.ballerina.projects.environment.LockingMode; + +record UnresolvedNode(ResolutionEngine.DependencyNode dependencyNode, LockingMode lockingMode) { +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java index 66b48b027541..8e85043e3567 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; // TODO: index should merge the central index and the distribution index. public class Index { @@ -38,11 +39,20 @@ public Index() { } public Optional getVersion(PackageOrg orgName, PackageName packageName, PackageVersion version) { + return getVersion(orgName, packageName, version, null); + } + + public Optional getVersion(PackageOrg orgName, PackageName packageName, PackageVersion version, + String repository) { List packageMap = this.packageMap.get(orgName + "/" + packageName); if (packageMap == null) { return Optional.empty(); } - return packageMap.stream().filter(pkg -> pkg.descriptor().version().equals(version)).findFirst(); + Stream versions = packageMap.stream().filter(pkg -> pkg.version().equals(version)); + if (repository != null) { + return versions.filter(pkg -> repository.equals(pkg.repository())).findFirst(); + } + return versions.findFirst(); } public void putVersion(IndexPackage pkg) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java index d38028bf82c0..6ef9b0d08d67 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java @@ -27,52 +27,53 @@ import java.util.List; public class IndexPackage { - private final PackageDescriptor descriptor; + private final PackageOrg packageOrg; + private final PackageName packageName; + private final PackageVersion packageVersion; + private final String repository; private final SemanticVersion ballerinaVersion; private final List dependencies; // TODO: add other fields as necessary - private IndexPackage(PackageDescriptor descriptor, - SemanticVersion ballerinaVersion, - List dependencies) { - this.descriptor = descriptor; - this.ballerinaVersion = ballerinaVersion; - this.dependencies = dependencies; - } - - public static IndexPackage from( + private IndexPackage( PackageOrg packageOrg, PackageName packageName, PackageVersion packageVersion, + String repository, SemanticVersion ballerinaVersion, List dependencies) { - return new IndexPackage( - PackageDescriptor.from(packageOrg, packageName, packageVersion), - ballerinaVersion, - dependencies); + this.packageOrg = packageOrg; + this.packageName = packageName; + this.packageVersion = packageVersion; + this.repository = repository; + this.ballerinaVersion = ballerinaVersion; + this.dependencies = dependencies; } public static IndexPackage from( - PackageDescriptor descriptor, + PackageOrg packageOrg, + PackageName packageName, + PackageVersion packageVersion, + String repository, SemanticVersion ballerinaVersion, List dependencies) { - return new IndexPackage(descriptor, ballerinaVersion, dependencies); + return new IndexPackage(packageOrg, packageName, packageVersion, repository, ballerinaVersion, dependencies); } public PackageName name() { - return descriptor.name(); + return packageName; } public PackageOrg org() { - return descriptor.org(); + return packageOrg; } public PackageVersion version() { - return descriptor.version(); + return packageVersion; } - public PackageDescriptor descriptor() { - return descriptor; + public String repository() { + return repository; } public List dependencies() { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java index 760313d232fb..3ede3002f061 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java @@ -1329,6 +1329,24 @@ public static boolean getSticky(Project project) { return false; } + public static boolean isWithin24HoursOfLastBuild(Project project) { + if (project.kind() == ProjectKind.BUILD_PROJECT) { + Path buildFilePath = project.targetDir().resolve(BUILD_FILE); + if (Files.exists(buildFilePath) && buildFilePath.toFile().length() > 0) { + try { + BuildJson buildJson = readBuildJson(buildFilePath); + // if distribution is not same, we anyway return sticky as false + if (buildJson != null && !buildJson.isExpiredLastUpdateTime()) { + return true; + } + } catch (IOException | JsonSyntaxException e) { + // ignore + } + } + } + return false; + } + /** * From a list of versions, get the versions within the compatible range. * diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/AbstractPackageResolutionTest.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/AbstractPackageResolutionTest.java index 436ed633197f..e3e639dbfd6a 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/AbstractPackageResolutionTest.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/AbstractPackageResolutionTest.java @@ -18,9 +18,11 @@ package io.ballerina.projects.test.resolution.packages; import io.ballerina.projects.DependencyGraph; +import io.ballerina.projects.ProjectException; import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; import io.ballerina.projects.test.resolution.packages.internal.GraphComparisonResult; import io.ballerina.projects.test.resolution.packages.internal.GraphUtils; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.test.resolution.packages.internal.PackageResolutionTestCase; import io.ballerina.projects.test.resolution.packages.internal.PackageResolutionTestCaseBuilder; import io.ballerina.projects.test.resolution.packages.internal.TestCaseFilePaths; @@ -43,15 +45,27 @@ public abstract class AbstractPackageResolutionTest { private static final Path RESOURCE_DIRECTORY = Path.of("src", "test", "resources", "package-resolution"); - public void runTestCase(String testSuiteDirName, String testCaseDirName, boolean sticky) { + public void runTestCase(String testSuiteDirName, String testCaseDirName, UpdatePolicy policy, + boolean lessThan24HrsAfterBuild, String errMsg) { Path testSuitePath = RESOURCE_DIRECTORY.resolve(testSuiteDirName); TestCaseFilePaths filePaths = TestCaseFilePathsBuilder.build(testSuitePath, testSuitePath.resolve(testCaseDirName)); - PackageResolutionTestCase resolutionTestCase = PackageResolutionTestCaseBuilder.build(filePaths, sticky); - DependencyGraph actualGraph = resolutionTestCase.execute(sticky); - DependencyGraph expectedGraph = resolutionTestCase.getExpectedGraph(sticky); - GraphComparisonResult compResult = GraphUtils.compareGraph(actualGraph, expectedGraph); - Assert.assertTrue(compResult.isIdenticalGraphs(), getDiagnosticLine(compResult.diagnostics())); + PackageResolutionTestCase resolutionTestCase = PackageResolutionTestCaseBuilder + .build(filePaths, policy, lessThan24HrsAfterBuild); + try { + DependencyGraph actualGraph = resolutionTestCase.execute(policy); + if (errMsg != null) { + Assert.fail("Expected a ProjectException with message: " + errMsg); + } + DependencyGraph expectedGraph = resolutionTestCase.getExpectedGraph(policy); + GraphComparisonResult compResult = GraphUtils.compareGraph(actualGraph, expectedGraph); + Assert.assertTrue(compResult.isIdenticalGraphs(), getDiagnosticLine(compResult.diagnostics())); + } catch (ProjectException err) { + if (errMsg == null) { + Assert.fail("No ProjectException expected, but got: " + err.getMessage()); + } + Assert.assertEquals(err.getMessage(), errMsg); + } } private String getDiagnosticLine(Collection diagnostics) { diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java index 73af87fe78c2..5d442ea751a8 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java @@ -1,5 +1,6 @@ package io.ballerina.projects.test.resolution.packages; +import io.ballerina.projects.environment.UpdatePolicy; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -10,63 +11,84 @@ */ public class ExistingProjectTests extends AbstractPackageResolutionTest { @Test(dataProvider = "resolutionTestCaseProvider") - public void testcaseExistingProjWithNoChanges(String testSuite, String testCase, boolean sticky) { - runTestCase(testSuite, testCase, sticky); + public void testcaseExistingProjWithNoChanges(String testSuite, String testCase, + UpdatePolicy mode, boolean lessThan24HrsAfterBuild, String errMsg) { + runTestCase(testSuite, testCase, mode, lessThan24HrsAfterBuild, errMsg); + } + + private static Object[] testCase(String testCase, UpdatePolicy policy) { + return new Object[]{"suite-existing_project", testCase, policy, false, null}; + } + + private static Object[] testCase(String testCase, UpdatePolicy policy, String errMsg) { + return new Object[]{"suite-existing_project", testCase, policy, false, errMsg}; } @DataProvider(name = "resolutionTestCaseProvider") public static Object[][] testCaseProvider() { return new Object[][]{ // 1. A new patch and minor version of a transitive has been released to central - {"suite-existing_project", "case-0001", true}, - {"suite-existing_project", "case-0001", false}, + testCase("case-0001", UpdatePolicy.SOFT), + testCase("case-0001", UpdatePolicy.MEDIUM), + testCase("case-0001", UpdatePolicy.HARD), + testCase("case-0001", UpdatePolicy.LOCKED), // 2. Adding of a new import which is already there as a transitive in the graph with an old version - {"suite-existing_project", "case-0002", true}, - {"suite-existing_project", "case-0002", false}, + testCase("case-0002", UpdatePolicy.SOFT), + testCase("case-0002", UpdatePolicy.MEDIUM), + testCase("case-0002", UpdatePolicy.HARD), + testCase("case-0002", UpdatePolicy.LOCKED, "Invalid state"), // 3. Remove existing import which is also a transitive dependency from another import - {"suite-existing_project", "case-0003", true}, - {"suite-existing_project", "case-0003", false}, + testCase("case-0003", UpdatePolicy.SOFT), + testCase("case-0003", UpdatePolicy.MEDIUM), + testCase("case-0003", UpdatePolicy.HARD), + testCase("case-0003", UpdatePolicy.LOCKED), // 4. Package contains a built-in transitive dependency with a non-zero version - {"suite-existing_project", "case-0004", true}, - {"suite-existing_project", "case-0004", false}, + testCase("case-0004", UpdatePolicy.SOFT), + testCase("case-0004", UpdatePolicy.MEDIUM), + testCase("case-0004", UpdatePolicy.HARD), + testCase("case-0004", UpdatePolicy.LOCKED), // 5. Package contains a built-in transitive dependency which has its dependencies changed // in the current dist - {"suite-existing_project", "case-0005", true}, - {"suite-existing_project", "case-0005", false}, - // 6. Remove existing import which also is a dependency of a newer patch version of another import - {"suite-existing_project", "case-0006", true}, - {"suite-existing_project", "case-0006", false}, - // 7. Package contains hierarchical imports - {"suite-existing_project", "case-0007", true}, - {"suite-existing_project", "case-0007", false}, - // 8. Add a new hierarchical import which has a possible package name in the Dependencies.toml - {"suite-existing_project", "case-0008", true}, - {"suite-existing_project", "case-0008", false}, - // 9. Package uses a module available in the newer minor version of an existing dependency - {"suite-existing_project", "case-0009", true}, - {"suite-existing_project", "case-0009", false}, - // 10. package contains dependencies with pre-release versions - {"suite-existing_project", "case-0010", true}, - {"suite-existing_project", "case-0010", false}, - // 11. package contains dependencies with pre-release versions specified from local repo - {"suite-existing_project", "case-0011", true}, - {"suite-existing_project", "case-0011", false}, - // 12. package contains dependencies which only has pre-release versions published - {"suite-existing_project", "case-0012", true}, - {"suite-existing_project", "case-0012", false}, - // 13. package contains dependency which specified in the Ballerina toml file thats not local - {"suite-existing_project", "case-0013", true}, - {"suite-existing_project", "case-0013", false}, - // 14. package contains 2 dependencies one of which is in Ballerina toml file thats not local - {"suite-existing_project", "case-0014", true}, - {"suite-existing_project", "case-0014", false}, - // 15. package updates transitive dependency from the Ballerina toml file that is not local - {"suite-existing_project", "case-0015", true}, - {"suite-existing_project", "case-0015", false}, - // 16. package name is hierarchical, there are new versions in the central, - // and the older version is specified in Ballerina.toml and Dependencies.toml - {"suite-existing_project", "case-0016", true}, - {"suite-existing_project", "case-0016", false} + testCase("case-0005", UpdatePolicy.SOFT), + testCase("case-0005", UpdatePolicy.MEDIUM), + testCase("case-0005", UpdatePolicy.HARD), + testCase("case-0005", UpdatePolicy.LOCKED, "Cannot have new dependencies with the LOCKED update policy"), +// // 6. Remove existing import which also is a dependency of a newer patch version of another import +// {"suite-existing_project", "case-0006", true}, +// {"suite-existing_project", "case-0006", false}, +// // 7. Package contains hierarchical imports +// {"suite-existing_project", "case-0007", true}, +// {"suite-existing_project", "case-0007", false}, +// // 8. Add a new hierarchical import which has a possible package name in the Dependencies.toml +// {"suite-existing_project", "case-0008", true}, +// {"suite-existing_project", "case-0008", false}, +// // 9. Package uses a module available in the newer minor version of an existing dependency +// {"suite-existing_project", "case-0009", true}, +// {"suite-existing_project", "case-0009", false}, +// // 10. package contains dependencies with pre-release versions +// {"suite-existing_project", "case-0010", true}, +// {"suite-existing_project", "case-0010", false}, +// // 11. package contains dependencies with pre-release versions specified from local repo +// {"suite-existing_project", "case-0011", true}, +// {"suite-existing_project", "case-0011", false}, +// // 12. package contains dependencies which only has pre-release versions published +// {"suite-existing_project", "case-0012", true}, +// {"suite-existing_project", "case-0012", false}, +// // 13. package contains dependency which specified in the Ballerina toml file thats not local +// {"suite-existing_project", "case-0013", true}, +// {"suite-existing_project", "case-0013", false}, +// // 14. package contains 2 dependencies one of which is in Ballerina toml file thats not local +// {"suite-existing_project", "case-0014", true}, +// {"suite-existing_project", "case-0014", false}, +// // 15. package updates transitive dependency from the Ballerina toml file that is not local +// {"suite-existing_project", "case-0015", true}, +// {"suite-existing_project", "case-0015", false}, +// // 16. package name is hierarchical, there are new versions in the central, +// // and the older version is specified in Ballerina.toml and Dependencies.toml +// {"suite-existing_project", "case-0016", true}, +// {"suite-existing_project", "case-0016", false}, +// {"suite-existing_project", "case-0017", true}, +// {"suite-existing_project", "case-0017", false} }; } } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Constants.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Constants.java index f42447b178d5..902d26abcea7 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Constants.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Constants.java @@ -32,8 +32,11 @@ public final class Constants { public static final String DEPS_TOML_FILE_NAME = "Dependencies_toml.dot"; public static final String BAL_TOML_FILE_NAME = "Ballerina_toml.dot"; public static final String INDEX_FILE_NAME = "index.dot"; - public static final String EXP_GRAPH_STICKY_FILE_NAME = "expected-graph-sticky.dot"; - public static final String EXP_GRAPH_NO_STICKY_FILE_NAME = "expected-graph-nosticky.dot"; + public static final String LOCAL_INDEX_FILE_NAME = "index-local.dot"; + public static final String EXP_GRAPH_SOFT_FILE_NAME = "expected-graph-soft.dot"; + public static final String EXP_GRAPH_MEDIUM_FILE_NAME = "expected-graph-medium.dot"; + public static final String EXP_GRAPH_HARD_FILE_NAME = "expected-graph-hard.dot"; + public static final String EXP_GRAPH_LOCKED_FILE_NAME = "expected-graph-locked.dot"; private Constants() { } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java index 78a9f7ca36ae..680a6f6a0518 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java @@ -22,6 +22,7 @@ import io.ballerina.projects.environment.ModuleLoadRequest; import io.ballerina.projects.environment.PackageResolver; import io.ballerina.projects.environment.ResolutionOptions; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.BlendedManifest; import io.ballerina.projects.internal.ModuleResolver; import io.ballerina.projects.internal.ResolutionEngine; @@ -42,50 +43,62 @@ public class PackageResolutionTestCase { private final PackageResolver packageResolver; private final ModuleResolver moduleResolver; private final Index index; + private final boolean hasDependencyManifest; + private final boolean distributionChange; + private final boolean lessThan24HrsAfterBuild; private final Collection moduleLoadRequests; - private final DependencyGraph expectedGraphSticky; - private final DependencyGraph expectedGraphNoSticky; + private final DependencyGraph expectedGraphSoft; + private final DependencyGraph expectedGraphMedium; + private final DependencyGraph expectedGraphHard; + private final DependencyGraph expectedGraphLocked; public PackageResolutionTestCase(PackageDescriptor rootPkgDesc, BlendedManifest blendedManifest, PackageResolver packageResolver, ModuleResolver moduleResolver, Index index, + boolean hasDependencyManifest, + boolean distributionChange, + boolean lessThan24HrsAfterBuild, Collection moduleLoadRequests, - DependencyGraph expectedGraphSticky, - DependencyGraph expectedGraphNoSticky) { + DependencyGraph expectedGraphSoft, + DependencyGraph expectedGraphMedium, + DependencyGraph expectedGraphHard, + DependencyGraph expectedGraphLocked) { this.rootPkgDesc = rootPkgDesc; this.blendedManifest = blendedManifest; this.packageResolver = packageResolver; this.moduleResolver = moduleResolver; this.index = index; + this.hasDependencyManifest = hasDependencyManifest; + this.distributionChange = distributionChange; + this.lessThan24HrsAfterBuild = lessThan24HrsAfterBuild; this.moduleLoadRequests = moduleLoadRequests; - this.expectedGraphSticky = expectedGraphSticky; - this.expectedGraphNoSticky = expectedGraphNoSticky; + this.expectedGraphSoft = expectedGraphSoft; + this.expectedGraphMedium = expectedGraphMedium; + this.expectedGraphHard = expectedGraphHard; + this.expectedGraphLocked = expectedGraphLocked; } - public DependencyGraph execute(boolean sticky) { - ResolutionOptions options = ResolutionOptions.builder().setOffline(true).setSticky(sticky).build(); + public DependencyGraph execute(UpdatePolicy policy) { + ResolutionOptions options = ResolutionOptions.builder().setOffline(true).setUpdatePolicy(policy).build(); ResolutionEngine resolutionEngine = new ResolutionEngine(rootPkgDesc, blendedManifest, - packageResolver, moduleResolver, options, index, true); + packageResolver, moduleResolver, options, index, hasDependencyManifest, distributionChange, + lessThan24HrsAfterBuild, true); return resolutionEngine.resolveDependencies(moduleLoadRequests); } - public DependencyGraph getExpectedGraph(boolean sticky) { - if (sticky) { - if (expectedGraphSticky == null) { - throw new IllegalStateException(Constants.EXP_GRAPH_STICKY_FILE_NAME + - " file cannot be found in the test case"); - } else { - return expectedGraphSticky; - } - } else { - if (expectedGraphNoSticky == null) { - throw new IllegalStateException(Constants.EXP_GRAPH_NO_STICKY_FILE_NAME + - " file cannot be found in the test case"); - } else { - return expectedGraphNoSticky; - } + public DependencyGraph getExpectedGraph(UpdatePolicy policy) { + DependencyGraph graph = switch (policy) { + case SOFT -> expectedGraphSoft; + case MEDIUM -> expectedGraphMedium; + case HARD -> expectedGraphHard; + case LOCKED -> expectedGraphLocked; + }; + if (graph == null) { + throw new IllegalStateException( + "Expected graph for " + policy + " policy cannot be found in the test case"); } + return graph; } } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java index 2b655fe4de2b..d379278ee8d1 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java @@ -31,6 +31,7 @@ import io.ballerina.projects.environment.ModuleLoadRequest; import io.ballerina.projects.environment.PackageCache; import io.ballerina.projects.environment.ResolutionOptions; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.BlendedManifest; import io.ballerina.projects.internal.ModuleResolver; import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; @@ -61,7 +62,9 @@ public final class PackageResolutionTestCaseBuilder { private PackageResolutionTestCaseBuilder() { } - public static PackageResolutionTestCase build(TestCaseFilePaths filePaths, boolean sticky) { + public static PackageResolutionTestCase build(TestCaseFilePaths filePaths, + UpdatePolicy policy, + boolean lessThan24HrsAfterBuild) { // Create PackageResolver DotGraphBasedPackageResolver packageResolver = buildPackageResolver(filePaths); @@ -75,29 +78,38 @@ public static PackageResolutionTestCase build(TestCaseFilePaths filePaths, boole // Create dependencyManifest DependencyManifest dependencyManifest = getDependencyManifest( filePaths.dependenciesTomlPath().orElse(null)); + boolean hasDependencyManifest = filePaths.dependenciesTomlPath().isPresent(); // Create packageManifest PackageManifest packageManifest = getPackageManifest( filePaths.ballerinaTomlPath().orElse(null), rootPkgDes); - Index index = getPackageIndex(filePaths.indexPath().orElse(null)); + Index index = getPackageIndex(filePaths.indexPath().orElse(null), filePaths.localIndexPath().orElse(null)); - // Create expected dependency graph with sticky - DependencyGraph expectedGraphSticky = getPkgDescGraph( - filePaths.expectedGraphStickyPath().orElse(null)); + // Create expected dependency graph with soft policy + DependencyGraph expectedGraphSoft = getPkgDescGraph( + filePaths.expectedGraphSoftPath().orElse(null)); - // Create expected dependency graph with no sticky - DependencyGraph expectedGraphNoSticky = getPkgDescGraph( - filePaths.expectedGraphNoStickyPath().orElse(null)); + // Create expected dependency graph with medium policy + DependencyGraph expectedGraphMedium = getPkgDescGraph( + filePaths.expectedGraphMediumPath().orElse(null)); + + // Create expected dependency graph with hard policy + DependencyGraph expectedGraphHard = getPkgDescGraph( + filePaths.expectedGraphHardPath().orElse(null)); + + // Create expected dependency graph with locked policy + DependencyGraph expectedGraphLocked = getPkgDescGraph( + filePaths.expectedGraphLockedPath().orElse(null)); BlendedManifest blendedManifest = BlendedManifest.from(dependencyManifest, packageManifest, packageResolver.localRepo(), new HashMap<>(), false); ModuleResolver moduleResolver = new ModuleResolver(rootPkgDes, getModulesInRootPackage(rootPkgDescWrapper, rootPkgDes), - blendedManifest, packageResolver, ResolutionOptions.builder().setSticky(sticky).build()); + blendedManifest, packageResolver, ResolutionOptions.builder().setUpdatePolicy(policy).build()); return new PackageResolutionTestCase(rootPkgDes, blendedManifest, - packageResolver, moduleResolver, index, moduleLoadRequests, - expectedGraphSticky, expectedGraphNoSticky); + packageResolver, moduleResolver, index, hasDependencyManifest, false, lessThan24HrsAfterBuild, + moduleLoadRequests, expectedGraphSoft, expectedGraphMedium, expectedGraphHard, expectedGraphLocked); } private static List getModulesInRootPackage(PackageDescWrapper rootPkgDescWrapper, @@ -194,16 +206,25 @@ private static PackageManifest getPackageManifest(Path balTomlPath, PackageDescr return PackageManifest.from(rootPkgDesc, null, null, Collections.emptyMap(), dependencies); } - private static Index getPackageIndex(Path indexPath) { - if (indexPath == null) { - return Index.EMPTY_INDEX; - } - MutableGraph repoDotGraph = DotGraphUtils.createGraph(indexPath); + private static Index getPackageIndex(Path indexPath, Path localIndexPath) { Index index = new Index(); - for (MutableGraph packageGraph : repoDotGraph.graphs()) { - IndexPackage indexPackage = Utils.getIndexPkgFromNode(packageGraph.name(), packageGraph.graphAttrs(), packageGraph.nodes()); - index.putVersion(indexPackage); - // TODO: Consider modules when introducing module constraint + if (indexPath != null) { + MutableGraph repoDotGraph = DotGraphUtils.createGraph(indexPath); + for (MutableGraph packageGraph : repoDotGraph.graphs()) { + IndexPackage indexPackage = Utils.getIndexPkgFromNode(packageGraph.name(), null, + packageGraph.graphAttrs(), packageGraph.nodes()); + index.putVersion(indexPackage); + // TODO: Consider modules when introducing module constraint + } + } + if (localIndexPath != null) { + MutableGraph repoDotGraph = DotGraphUtils.createGraph(localIndexPath); + for (MutableGraph packageGraph : repoDotGraph.graphs()) { + IndexPackage indexPackage = Utils.getIndexPkgFromNode(packageGraph.name(), "local", + packageGraph.graphAttrs(), packageGraph.nodes()); + index.putVersion(indexPackage); + // TODO: Consider modules when introducing module constraint + } } return index; } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/TestCaseFilePaths.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/TestCaseFilePaths.java index b040ca55152d..77502b8bd91a 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/TestCaseFilePaths.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/TestCaseFilePaths.java @@ -35,8 +35,11 @@ public class TestCaseFilePaths { private final Path dependenciesTomlPath; private final Path ballerinaTomlPath; private final Path indexPath; - private final Path expectedGraphStickyPath; - private final Path expectedGraphNoStickyPath; + private final Path localIndexPath; + private final Path expectedGraphSoftPath; + private final Path expectedGraphMediumPath; + private final Path expectedGraphHardPath; + private final Path expectedGraphLockedPath; TestCaseFilePaths(Path centralRepoPath, Path distRepoPath, @@ -44,18 +47,24 @@ public class TestCaseFilePaths { Path appPath, Path dependenciesTomlPath, Path indexPath, + Path localIndexPath, Path ballerinaTomlPath, - Path expectedGraphStickyPath, - Path expectedGraphNoStickyPath) { + Path expectedGraphSoftPath, + Path expectedGraphMediumPath, + Path expectedGraphHardPath, + Path expectedGraphLockedPath) { this.centralRepoPath = centralRepoPath; this.distRepoPath = distRepoPath; this.localRepoDirPath = localRepoDirPath; this.appPath = appPath; this.dependenciesTomlPath = dependenciesTomlPath; this.indexPath = indexPath; + this.localIndexPath = localIndexPath; this.ballerinaTomlPath = ballerinaTomlPath; - this.expectedGraphStickyPath = expectedGraphStickyPath; - this.expectedGraphNoStickyPath = expectedGraphNoStickyPath; + this.expectedGraphSoftPath = expectedGraphSoftPath; + this.expectedGraphMediumPath = expectedGraphMediumPath; + this.expectedGraphHardPath = expectedGraphHardPath; + this.expectedGraphLockedPath = expectedGraphLockedPath; } public Optional centralRepoPath() { @@ -86,12 +95,24 @@ public Optional indexPath() { return Optional.ofNullable(indexPath); } - public Optional expectedGraphStickyPath() { - return Optional.ofNullable(expectedGraphStickyPath); + public Optional localIndexPath() { + return Optional.ofNullable(localIndexPath); } - public Optional expectedGraphNoStickyPath() { - return Optional.ofNullable(expectedGraphNoStickyPath); + public Optional expectedGraphSoftPath() { + return Optional.ofNullable(expectedGraphSoftPath); + } + + public Optional expectedGraphMediumPath() { + return Optional.ofNullable(expectedGraphMediumPath); + } + + public Optional expectedGraphHardPath() { + return Optional.ofNullable(expectedGraphHardPath); + } + + public Optional expectedGraphLockedPath() { + return Optional.ofNullable(expectedGraphLockedPath); } /** @@ -124,16 +145,23 @@ public static TestCaseFilePaths build(Path testSuitePath, Path testCasePath) { Path.of(Constants.REPO_DIR_NAME).resolve(Constants.LOCAL_REPO_DIR_NAME)); Path indexPath = getFilePath(testSuitePath, testCasePath, Path.of(Constants.REPO_DIR_NAME).resolve(Constants.INDEX_FILE_NAME)); + Path localIndexPath = getFilePath(testSuitePath, testCasePath, + Path.of(Constants.REPO_DIR_NAME).resolve(Constants.LOCAL_INDEX_FILE_NAME)); Path depsTomlPath = getFilePath(testSuitePath, testCasePath, Path.of(Constants.DEPS_TOML_FILE_NAME)); Path balTomlPath = getFilePath(testSuitePath, testCasePath, Path.of(Constants.BAL_TOML_FILE_NAME)); - Path expGraphStickyPath = getFilePath(testSuitePath, testCasePath, - Path.of(Constants.EXP_GRAPH_STICKY_FILE_NAME)); - Path expGraphNoStickyPath = getFilePath(testSuitePath, testCasePath, - Path.of(Constants.EXP_GRAPH_NO_STICKY_FILE_NAME)); - - return new TestCaseFilePaths(centralRepoPath, distRepoPath, localRepoDirPath, - appPath, depsTomlPath, indexPath, balTomlPath, expGraphStickyPath, expGraphNoStickyPath); + Path expGraphSoftPath = getFilePath(testSuitePath, testCasePath, + Path.of(Constants.EXP_GRAPH_SOFT_FILE_NAME)); + Path expGraphMediumPath = getFilePath(testSuitePath, testCasePath, + Path.of(Constants.EXP_GRAPH_MEDIUM_FILE_NAME)); + Path expGraphHardPath = getFilePath(testSuitePath, testCasePath, + Path.of(Constants.EXP_GRAPH_HARD_FILE_NAME)); + Path expGraphLockedPath = getFilePath(testSuitePath, testCasePath, + Path.of(Constants.EXP_GRAPH_LOCKED_FILE_NAME)); + + return new TestCaseFilePaths(centralRepoPath, distRepoPath, localRepoDirPath, appPath, depsTomlPath, + indexPath, localIndexPath, balTomlPath, expGraphSoftPath, expGraphMediumPath, + expGraphHardPath, expGraphLockedPath); } private static Path getFilePath(Path testSuitePath, Path testCasePath, Path relativeFilePath) { diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java index b87d4acdf43e..738c85a0c54a 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java @@ -89,8 +89,12 @@ public static ModuleLoadRequest getModuleLoadRequest(String name, return new ModuleLoadRequest(PackageOrg.from(split[0]), split[1], scope, resolutionType); } - public static IndexPackage getIndexPkgFromNode(Label name, MutableAttributed attrs, Collection nodes) { - PackageDescriptor descriptor = getPkgDescFromNode(name.toString()); + public static IndexPackage getIndexPkgFromNode(Label name, + String repository, + MutableAttributed attrs, + Collection nodes) { + String[] split = name.toString().split("/"); + String[] split1 = split[1].split(":"); String balVersionStr = "2201.0.0"; if (attrs.get("ballerinaVersion") != null) { balVersionStr = Objects.requireNonNull(attrs.get("ballerinaVersion")).toString(); @@ -104,7 +108,13 @@ public static IndexPackage getIndexPkgFromNode(Label name, MutableAttributed "ballerina/cache:1.2.1" + "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" + "samejs/app:0.1.0" -> "ballerina/http:1.3.1" + "samejs/app:0.1.0" -> "samjs/foo:1.2.1" + "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "samjs/foo:1.2.1" -> "samjs/bar:1.3.4" + "samjs/foo:1.2.1" -> "samjs/bazz:1.4.4" + "samtest/io:1.5.1" -> "ballerina/http:1.3.1" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + "samjs/b:1.3.4" -> "samjs/c:1.4.5" + "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" + "samjs/bar:1.3.4" -> "samjs/p:1.3.4" + "samjs/bar:1.3.4" -> "samjs/q:1.4.4" + "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" + "samjs/bazz:1.4.4" -> "samjs/c:1.4.5" + + "samjs/foo:1.2.1" [scope = "testOnly"] + "samjs/bar:1.3.4" [scope = "testOnly"] + "samjs/bazz:1.4.4" [scope = "testOnly"] + "samjs/b:1.3.4" [scope = "testOnly"] + "samjs/p:1.3.4" [scope = "testOnly"] + "samtest/io:1.5.1" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-medium.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-medium.dot index 2ac234df36d8..8f2d63cb9ec7 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-nosticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-medium.dot @@ -1,20 +1,20 @@ digraph "example1" { - "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" - "ballerina/http:1.3.1" -> "ballerina/io:1.0.2" "samejs/app:0.1.0" -> "ballerina/http:1.3.1" "samejs/app:0.1.0" -> "samjs/foo:1.2.1" "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" + "ballerina/http:1.3.1" -> "ballerina/io:1.0.2" "samjs/foo:1.2.1" -> "samjs/bar:1.3.4" "samjs/foo:1.2.1" -> "samjs/bazz:1.4.4" "samtest/io:1.5.1" -> "ballerina/http:1.3.1" "samtest/io:1.5.1" -> "samjs/c:1.4.5" "samtest/io:1.5.1" -> "samjs/q:1.4.4" - "samjs/b:1.3.4" -> "samjs/c:1.4.5" "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" "samjs/bar:1.3.4" -> "samjs/p:1.3.4" "samjs/bar:1.3.4" -> "samjs/q:1.4.4" "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" "samjs/bazz:1.4.4" -> "samjs/c:1.4.5" + "samjs/b:1.3.4" -> "samjs/c:1.4.5" "samjs/foo:1.2.1" [scope = "testOnly"] "samjs/bar:1.3.4" [scope = "testOnly"] diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-soft.dot new file mode 100644 index 000000000000..a8d3fe5978b6 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0001/expected-graph-soft.dot @@ -0,0 +1,25 @@ +digraph "example1" { + "ballerina/http:1.4.0" -> "ballerina/io:1.2.0" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" + "samejs/app:0.1.0" -> "ballerina/http:1.4.0" + "samejs/app:0.1.0" -> "samjs/foo:1.3.0" + "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "samjs/foo:1.3.0" -> "samjs/bar:1.3.4" + "samjs/foo:1.3.0" -> "samjs/bazz:1.4.4" + "samtest/io:1.5.1" -> "ballerina/http:1.4.0" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + "samjs/b:1.3.4" -> "samjs/c:1.4.5" + "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" + "samjs/bar:1.3.4" -> "samjs/p:1.3.4" + "samjs/bar:1.3.4" -> "samjs/q:1.4.4" + "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" + "samjs/bazz:1.4.4" -> "samjs/c:1.4.5" + + "samjs/foo:1.3.0" [scope = "testOnly"] + "samjs/bar:1.3.4" [scope = "testOnly"] + "samjs/bazz:1.4.4" [scope = "testOnly"] + "samjs/b:1.3.4" [scope = "testOnly"] + "samjs/p:1.3.4" [scope = "testOnly"] + "samtest/io:1.5.1" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/Dependencies_toml.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/Dependencies_toml.dot index b0b36b7acd1f..1bcd5faa831b 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/Dependencies_toml.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/Dependencies_toml.dot @@ -1,9 +1,9 @@ digraph "example1" { - "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" - "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" "samejs/app:0.1.0" -> "ballerina/http:1.3.1" "samejs/app:0.1.0" -> "samjs/foo:1.2.1" "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" + "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" "samjs/foo:1.2.1" -> "samjs/bar:1.3.4" "samjs/foo:1.2.1" -> "samjs/bazz:1.4.4" "samtest/io:1.5.1" -> "ballerina/http:1.3.1" diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/case-description.md index c1614afacdea..f723dd9dacf3 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/case-description.md +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/case-description.md @@ -7,9 +7,11 @@ ## Expected behavior -### Sticky == true +### Update policy == SOFT +Dependency graph should be updated to have `ballerina/cache:1.4.0`, `ballerina/io:1.2.0` `ballerina/http:1.4.0`, and `samjs/foo:1.3.0`. +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerina/cache:1.4.0`, `ballerina/io:1.0.2`. +### Update policy == HARD Dependency graph should be updated to have `ballerina/cache:1.4.0`, no changes to `ballerina/io:1.0.1` - -### Sticky == false -Dependency graph should be updated to have `ballerina/cache:1.4.0`, `ballerina/io:1.0.2` - +### Update policy == LOCKED +Build failure since import addition is not allowed in the LOCKED update policy. diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-hard.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-hard.dot index 133bd4d00f80..ead03e3759e3 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-sticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-hard.dot @@ -1,10 +1,10 @@ digraph "example1" { - "ballerina/http:1.3.1" -> "ballerina/cache:1.4.0" - "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" "samejs/app:0.1.0" -> "ballerina/cache:1.4.0" "samejs/app:0.1.0" -> "ballerina/http:1.3.1" "samejs/app:0.1.0" -> "samjs/foo:1.2.1" "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "ballerina/http:1.3.1" -> "ballerina/cache:1.4.0" + "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" "samjs/foo:1.2.1" -> "samjs/bar:1.3.4" "samjs/foo:1.2.1" -> "samjs/bazz:1.4.4" "samtest/io:1.5.1" -> "ballerina/http:1.3.1" diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-medium.dot similarity index 95% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-medium.dot index 3d0f5096b271..bbeefe8437db 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-nosticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-medium.dot @@ -1,10 +1,10 @@ digraph "example1" { - "ballerina/http:1.3.1" -> "ballerina/cache:1.4.0" - "ballerina/http:1.3.1" -> "ballerina/io:1.0.2" "samejs/app:0.1.0" -> "ballerina/cache:1.4.0" "samejs/app:0.1.0" -> "ballerina/http:1.3.1" "samejs/app:0.1.0" -> "samjs/foo:1.2.1" "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "ballerina/http:1.3.1" -> "ballerina/cache:1.4.0" + "ballerina/http:1.3.1" -> "ballerina/io:1.0.2" "samjs/foo:1.2.1" -> "samjs/bar:1.3.4" "samjs/foo:1.2.1" -> "samjs/bazz:1.4.4" "samtest/io:1.5.1" -> "ballerina/http:1.3.1" @@ -23,4 +23,5 @@ digraph "example1" { "samjs/b:1.3.4" [scope = "testOnly"] "samjs/p:1.3.4" [scope = "testOnly"] "samtest/io:1.5.1" [repo = "local"] + "ballerina/cache:1.4.0" [transitive = true] } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-soft.dot new file mode 100644 index 000000000000..0172c0983d41 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0002/expected-graph-soft.dot @@ -0,0 +1,26 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/cache:1.4.0" + "samejs/app:0.1.0" -> "ballerina/http:1.4.0" + "samejs/app:0.1.0" -> "samjs/foo:1.3.0" + "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "ballerina/http:1.4.0" -> "ballerina/io:1.2.0" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" + "samjs/foo:1.3.0" -> "samjs/bar:1.3.4" + "samjs/foo:1.3.0" -> "samjs/bazz:1.4.4" + "samtest/io:1.5.1" -> "ballerina/http:1.4.0" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + "samjs/b:1.3.4" -> "samjs/c:1.4.5" + "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" + "samjs/bar:1.3.4" -> "samjs/p:1.3.4" + "samjs/bar:1.3.4" -> "samjs/q:1.4.4" + "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" + "samjs/bazz:1.4.4" -> "samjs/c:1.4.5" + + "samjs/foo:1.3.0" [scope = "testOnly"] + "samjs/bar:1.3.4" [scope = "testOnly"] + "samjs/bazz:1.4.4" [scope = "testOnly"] + "samjs/b:1.3.4" [scope = "testOnly"] + "samjs/p:1.3.4" [scope = "testOnly"] + "samtest/io:1.5.1" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/case-description.md index 6a902b383d74..a711fe12a44a 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/case-description.md +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/case-description.md @@ -8,10 +8,11 @@ ## Expected behavior -### Sticky == true -No changes to the graph. -The latest version of `ballerina/cache` `1.4.0 ` is already locked in Dependencies.toml, no changes to `ballerina/io:1.0.1` - -### Sticky == false -Dependency graph should be updated to have `ballerina/io:1.0.2` - +### Update policy == SOFT +Dependency graph should be updated to have `ballerina/http:1.4.0`, `ballerina/io:1.2.0`, `samjs/foo:1.3.0`. +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerina/io:1.0.2`. +### Update policy == HARD +No changes to the dependency graph. +### Update policy == LOCKED +No changes to the dependency graph. diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-hard.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-hard.dot diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-locked.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-locked.dot new file mode 100644 index 000000000000..ab5b381d777f --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-locked.dot @@ -0,0 +1,25 @@ +digraph "example1" { + "ballerina/http:1.3.1" -> "ballerina/cache:1.4.0" + "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" + "samejs/app:0.1.0" -> "ballerina/http:1.3.1" + "samejs/app:0.1.0" -> "samjs/foo:1.2.1" + "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "samjs/foo:1.2.1" -> "samjs/bar:1.3.4" + "samjs/foo:1.2.1" -> "samjs/bazz:1.4.4" + "samtest/io:1.5.1" -> "ballerina/http:1.3.1" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + "samjs/b:1.3.4" -> "samjs/c:1.4.5" + "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" + "samjs/bar:1.3.4" -> "samjs/p:1.3.4" + "samjs/bar:1.3.4" -> "samjs/q:1.4.4" + "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" + "samjs/bazz:1.4.4" -> "samjs/c:1.4.5" + + "samjs/foo:1.2.1" [scope = "testOnly"] + "samjs/bar:1.3.4" [scope = "testOnly"] + "samjs/bazz:1.4.4" [scope = "testOnly"] + "samjs/b:1.3.4" [scope = "testOnly"] + "samjs/p:1.3.4" [scope = "testOnly"] + "samtest/io:1.5.1" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-medium.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-medium.dot diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-soft.dot new file mode 100644 index 000000000000..c174b31961b3 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0003/expected-graph-soft.dot @@ -0,0 +1,25 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/http:1.4.0" + "samejs/app:0.1.0" -> "samjs/foo:1.3.0" + "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "ballerina/http:1.4.0" -> "ballerina/io:1.2.0" + "samjs/foo:1.3.0" -> "samjs/bar:1.3.4" + "samjs/foo:1.3.0" -> "samjs/bazz:1.4.4" + "samtest/io:1.5.1" -> "ballerina/http:1.4.0" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" + "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" + "samjs/bar:1.3.4" -> "samjs/p:1.3.4" + "samjs/bar:1.3.4" -> "samjs/q:1.4.4" + "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" + "samjs/bazz:1.4.4" -> "samjs/c:1.4.5" + "samjs/b:1.3.4" -> "samjs/c:1.4.5" + + "samjs/foo:1.3.0" [scope = "testOnly"] + "samjs/bar:1.3.4" [scope = "testOnly"] + "samjs/bazz:1.4.4" [scope = "testOnly"] + "samjs/b:1.3.4" [scope = "testOnly"] + "samjs/p:1.3.4" [scope = "testOnly"] + "samtest/io:1.5.1" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/case-description.md index dfde5da1b957..94438bb39c7a 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/case-description.md +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/case-description.md @@ -1,6 +1,6 @@ # Package contains a built-in transitive dependency with a no-zero version -1. User's package has `ballerinai/transaction:1.0.15` as a transitive dependency (built with slbeta2) +1. User's package has `ballerinai/transaction:1.0.1` as a transitive dependency (built with slbeta2) and `ballerina/io:1.0.1` as a transitive dependency 2. Current distribution has `ballerinai/transaction:0.0.0` 3. A newer patch version `ballerina/io:1.0.2` has been released to central @@ -8,8 +8,11 @@ and `ballerina/io:1.0.1` as a transitive dependency ## Expected behavior -### Sticky == true -Dependency graph should be updated to have `ballerinai/transaction:0.0.0`, no changes to `ballerina/io:1.0.1` -### Sticky == false -Dependency graph should be updated to have `ballerinai/transaction:0.0.0` and `ballerina/io:1.0.2` - +### Update policy == SOFT +Dependency graph should be updated to have `ballerina/http:1.4.0`, `samjs/foo:1.3.0`, `ballerina/io:1.2.0`, `ballerinai/transaction:0.0.0`, `ballerina/cache:1.4.0`. +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerinai/transaction:0.0.0`, `ballerina/io:1.0.2` +### Update policy == HARD +Dependency graph should be updated to have `ballerinai/transaction:0.0.0`. +### Update policy == LOCKED +Dependency graph should be updated to have `ballerinai/transaction:0.0.0`. diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-hard.dot similarity index 94% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-hard.dot index 2f4080e958f4..39fc8223b70e 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-sticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-hard.dot @@ -1,7 +1,7 @@ digraph "example1" { "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" - "ballerinax/mysql:1.0.0" -> "ballerinai/transaction:1.0.2" + "ballerinax/mysql:1.0.0" -> "ballerinai/transaction:0.0.0" "samejs/app:0.1.0" -> "ballerinax/mysql:1.0.0" "samejs/app:0.1.0" -> "ballerina/http:1.3.1" "samejs/app:0.1.0" -> "samjs/foo:1.2.1" diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-locked.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-locked.dot new file mode 100644 index 000000000000..39fc8223b70e --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-locked.dot @@ -0,0 +1,27 @@ +digraph "example1" { + "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" + "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" + "ballerinax/mysql:1.0.0" -> "ballerinai/transaction:0.0.0" + "samejs/app:0.1.0" -> "ballerinax/mysql:1.0.0" + "samejs/app:0.1.0" -> "ballerina/http:1.3.1" + "samejs/app:0.1.0" -> "samjs/foo:1.2.1" + "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "samjs/foo:1.2.1" -> "samjs/bar:1.3.4" + "samjs/foo:1.2.1" -> "samjs/bazz:1.4.4" + "samtest/io:1.5.1" -> "ballerina/http:1.3.1" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + "samjs/b:1.3.4" -> "samjs/c:1.4.5" + "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" + "samjs/bar:1.3.4" -> "samjs/p:1.3.4" + "samjs/bar:1.3.4" -> "samjs/q:1.4.4" + "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" + "samjs/bazz:1.4.4" -> "samjs/c:1.4.5" + + "samjs/foo:1.2.1" [scope = "testOnly"] + "samjs/bar:1.3.4" [scope = "testOnly"] + "samjs/bazz:1.4.4" [scope = "testOnly"] + "samjs/b:1.3.4" [scope = "testOnly"] + "samjs/p:1.3.4" [scope = "testOnly"] + "samtest/io:1.5.1" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-medium.dot similarity index 94% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-medium.dot index 67aa59c61200..8c63db108d8a 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-nosticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-medium.dot @@ -1,7 +1,7 @@ digraph "example1" { "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" "ballerina/http:1.3.1" -> "ballerina/io:1.0.2" - "ballerinax/mysql:1.0.0" -> "ballerinai/transaction:1.0.2" + "ballerinax/mysql:1.0.0" -> "ballerinai/transaction:0.0.0" "samejs/app:0.1.0" -> "ballerinax/mysql:1.0.0" "samejs/app:0.1.0" -> "ballerina/http:1.3.1" "samejs/app:0.1.0" -> "samjs/foo:1.2.1" diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-soft.dot new file mode 100644 index 000000000000..2685c89d36ce --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0004/expected-graph-soft.dot @@ -0,0 +1,27 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerinax/mysql:1.0.0" + "samejs/app:0.1.0" -> "ballerina/http:1.4.0" + "samejs/app:0.1.0" -> "samjs/foo:1.3.0" + "samejs/app:0.1.0" -> "samtest/io:1.5.1" + "ballerina/http:1.4.0" -> "ballerina/io:1.2.0" + "ballerinax/mysql:1.0.0" -> "ballerinai/transaction:0.0.0" + "samjs/foo:1.3.0" -> "samjs/bar:1.3.4" + "samjs/foo:1.3.0" -> "samjs/bazz:1.4.4" + "samtest/io:1.5.1" -> "ballerina/http:1.4.0" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" + "samjs/b:1.3.4" -> "samjs/c:1.4.5" + "samjs/bar:1.3.4" -> "samjs/bazz:1.4.4" + "samjs/bar:1.3.4" -> "samjs/p:1.3.4" + "samjs/bar:1.3.4" -> "samjs/q:1.4.4" + "samjs/bazz:1.4.4" -> "samjs/b:1.3.4" + "samjs/bazz:1.4.4" -> "samjs/c:1.4.5" + + "samjs/foo:1.3.0" [scope = "testOnly"] + "samjs/bar:1.3.4" [scope = "testOnly"] + "samjs/bazz:1.4.4" [scope = "testOnly"] + "samjs/b:1.3.4" [scope = "testOnly"] + "samjs/p:1.3.4" [scope = "testOnly"] + "samtest/io:1.5.1" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/case-description.md index 31e6c7127b7c..6954d9dc8e96 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/case-description.md +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/case-description.md @@ -21,9 +21,11 @@ digraph "example1" { ## Expected behavior -### Sticky == true -Dependency graph should be updated to have `ballerinai/transaction:0.0.0` and `ballerina/cache:1.3.2` - -### Sticky == false -Same behavior as sticky ==true - +### Update policy == SOFT +Dependency graph should be updated to have `ballerinai/foo:0.0.0` and `ballerina/cache:1.4.0` +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerinai/foo:0.0.0` and `ballerina/cache:1.3.2` +### Update policy == HARD +Dependency graph should be updated to have `ballerinai/foo:0.0.0` and `ballerina/cache:1.3.1` +### Update policy == LOCKED +Build failure since import addition is not allowed in the LOCKED update policy. diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-hard.dot similarity index 74% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-hard.dot index 66aa1bb15b3e..685801d7a364 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-sticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-hard.dot @@ -2,5 +2,5 @@ digraph "example1" { "myOrg/app:0.1.0" -> "myOrg/bazz:1.0.0" "myOrg/bazz:1.0.0" -> "myOrg/bar:1.3.1" "myOrg/bar:1.3.1" -> "ballerinai/foo:0.0.0" - "ballerinai/foo:0.0.0" -> "ballerina/cache:1.3.2" + "ballerinai/foo:0.0.0" -> "ballerina/cache:1.3.1" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-medium.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-medium.dot diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-soft.dot new file mode 100644 index 000000000000..cd16d3e812e2 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0005/expected-graph-soft.dot @@ -0,0 +1,6 @@ +digraph "example1" { + "myOrg/app:0.1.0" -> "myOrg/bazz:1.0.0" + "myOrg/bazz:1.0.0" -> "myOrg/bar:1.3.1" + "myOrg/bar:1.3.1" -> "ballerinai/foo:0.0.0" + "ballerinai/foo:0.0.0" -> "ballerina/cache:1.4.0" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index-local.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index-local.dot new file mode 100644 index 000000000000..9d4b91068e35 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index-local.dot @@ -0,0 +1,8 @@ +digraph central { + subgraph "samtest/io:1.5.1" { + "ballerinaVersion"="2201.0.0"; + "samtest/io:1.5.1" -> "ballerina/http:1.3.1" + "samtest/io:1.5.1" -> "samjs/c:1.4.5" + "samtest/io:1.5.1" -> "samjs/q:1.4.4" + } +} \ No newline at end of file diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index.dot index 3ea82b262037..3110eb4ec8f4 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index.dot @@ -142,34 +142,26 @@ digraph "index" { "ballerinaVersion"="2201.0.0"; } - // MOVE THIS TO THE LOCAL REPO - subgraph "samtest/io:1.5.1" { - "ballerinaVersion"="2201.0.0"; - "samtest/io:1.5.1" -> "ballerina/http:1.3.1" - "samtest/io:1.5.1" -> "samjs/c:1.4.5" - "samtest/io:1.5.1" -> "samjs/q:1.4.4" - } - // MOVE THIS TO THE DIST REPO subgraph "ballerina/io:1.0.1" { } subgraph "ballerina/io:1.2.0" { - "ballerina/io:1.2.0" -> "ballerina/cache:1.3.1" + "ballerina/io:1.2.0" -> "ballerina/cache:1.3.1" } subgraph "ballerina/http:1.3.1" { - "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" - "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" + "ballerina/http:1.3.1" -> "ballerina/io:1.0.1" + "ballerina/http:1.3.1" -> "ballerina/cache:1.2.1" } subgraph "ballerina/auth:2.0.0" { - "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" } subgraph "ballerina/auth:2.0.1" { - "ballerina/auth:2.0.1" -> "ballerina/io:1.0.1" - "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.1" + "ballerina/auth:2.0.1" -> "ballerina/io:1.0.1" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.1" } subgraph "ballerina/cache:1.2.1" { From 7520ad63da44a8b3d9796213af2041c19d1efef9 Mon Sep 17 00:00:00 2001 From: Gayal Dassanayake Date: Mon, 6 Jan 2025 10:29:16 +0530 Subject: [PATCH 3/6] Fix tests 0006, 0007, 0008 --- .../packages/ExistingProjectTests.java | 24 ++++++++++++------- .../case-0006/case-description.md | 16 +++++++------ ...aph-sticky.dot => expected-graph-hard.dot} | 2 +- .../case-0006/expected-graph-locked.dot | 4 ++++ ...nosticky.dot => expected-graph-medium.dot} | 4 ++-- .../case-0006/expected-graph-soft.dot | 6 +++++ .../case-0007/case-description.md | 20 ++++++++++++++++ ...aph-sticky.dot => expected-graph-hard.dot} | 2 +- .../case-0007/expected-graph-locked.dot | 5 ++++ ...nosticky.dot => expected-graph-medium.dot} | 4 ++-- .../case-0007/expected-graph-soft.dot | 7 ++++++ .../case-0008/Dependencies_toml.dot | 2 +- .../case-0008/case-description.md | 21 ++++++++++++++++ ...aph-sticky.dot => expected-graph-hard.dot} | 0 ...nosticky.dot => expected-graph-medium.dot} | 4 ++-- .../case-0008/expected-graph-soft.dot | 8 +++++++ 16 files changed, 104 insertions(+), 25 deletions(-) rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/{expected-graph-sticky.dot => expected-graph-hard.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-locked.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/{expected-graph-nosticky.dot => expected-graph-medium.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-soft.dot create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/case-description.md rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/{expected-graph-sticky.dot => expected-graph-hard.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-locked.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/{expected-graph-nosticky.dot => expected-graph-medium.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-soft.dot create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/case-description.md rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/{expected-graph-sticky.dot => expected-graph-hard.dot} (100%) rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/{expected-graph-nosticky.dot => expected-graph-medium.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-soft.dot diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java index 5d442ea751a8..1b0c4a7d1192 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java @@ -53,15 +53,21 @@ public static Object[][] testCaseProvider() { testCase("case-0005", UpdatePolicy.MEDIUM), testCase("case-0005", UpdatePolicy.HARD), testCase("case-0005", UpdatePolicy.LOCKED, "Cannot have new dependencies with the LOCKED update policy"), -// // 6. Remove existing import which also is a dependency of a newer patch version of another import -// {"suite-existing_project", "case-0006", true}, -// {"suite-existing_project", "case-0006", false}, -// // 7. Package contains hierarchical imports -// {"suite-existing_project", "case-0007", true}, -// {"suite-existing_project", "case-0007", false}, -// // 8. Add a new hierarchical import which has a possible package name in the Dependencies.toml -// {"suite-existing_project", "case-0008", true}, -// {"suite-existing_project", "case-0008", false}, + // 6. Remove existing import which also is a dependency of a newer patch version of another import + testCase("case-0006", UpdatePolicy.SOFT), + testCase("case-0006", UpdatePolicy.MEDIUM), + testCase("case-0006", UpdatePolicy.HARD), + testCase("case-0006", UpdatePolicy.LOCKED), + // 7. Package contains hierarchical imports + testCase("case-0007", UpdatePolicy.SOFT), + testCase("case-0007", UpdatePolicy.MEDIUM), + testCase("case-0007", UpdatePolicy.HARD), + testCase("case-0007", UpdatePolicy.LOCKED), + // 8. Add a new hierarchical import which has a possible package name in the Dependencies.toml + testCase("case-0008", UpdatePolicy.SOFT), + testCase("case-0008", UpdatePolicy.MEDIUM), + testCase("case-0008", UpdatePolicy.HARD), + testCase("case-0008", UpdatePolicy.LOCKED, "Invalid state"), // // 9. Package uses a module available in the newer minor version of an existing dependency // {"suite-existing_project", "case-0009", true}, // {"suite-existing_project", "case-0009", false}, diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/case-description.md index b868bbeb88b9..1818873331fe 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/case-description.md +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/case-description.md @@ -3,16 +3,18 @@ 1. User has `ballerina/auth:2.0.0` and `ballerina/cache:1.2.1` in the project where, `ballerina/auth:2.0.0 -> ballerina/io:1.0.1` 3. Current distribution contains `ballerina/cache` versions: `1.2.1`, `1.3.1`, `1.3.2`, `1.4.0` 3. Central repo contains `ballerina/io` versions: `1.0.1`, `1.0.2`, `1.1.0` and -`ballerina/auth` versions: `2.0.0` and `2.0.1` where, `ballerina/auth:2.0.0 -> ballerina/cache:1.3.1` +`ballerina/auth` versions: `2.0.0` and `2.0.1` where, `ballerina/auth:2.0.1 -> ballerina/cache:1.3.1` 4. User removes the import `ballerina/cache` from the project ## Expected behavior -### Sticky == true -No changes to the graph. -`ballerina/cache` should be removed from the dependency graph - -### Sticky == false +### Update policy == SOFT +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.2.0` and `ballerina/cache:1.4.0` should +be newly added as a dependency of `ballerina/auth` +### Update policy == MEDIUM Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.0.2` and `ballerina/cache:1.3.2` should be newly added as a dependency of `ballerina/auth` - +### Update policy == HARD +No changes to the graph. `ballerina/cache` should be removed from the dependency graph +### Update policy == LOCKED +No changes to the graph. `ballerina/cache` should be removed from the dependency graph diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-hard.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-hard.dot index 4781e19b2ff2..a67eecec365a 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-sticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-hard.dot @@ -1,4 +1,4 @@ digraph "example1" { - "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-locked.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-locked.dot new file mode 100644 index 000000000000..a67eecec365a --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-locked.dot @@ -0,0 +1,4 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-medium.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-medium.dot index 97557355211c..be5663bbe0ef 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-nosticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-medium.dot @@ -1,5 +1,5 @@ digraph "example1" { - "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" - "ballerina/auth:2.0.1" -> "ballerina/io:1.0.2" "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "ballerina/auth:2.0.1" -> "ballerina/io:1.0.2" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-soft.dot new file mode 100644 index 000000000000..fdaaa7955f80 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0006/expected-graph-soft.dot @@ -0,0 +1,6 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "ballerina/auth:2.0.1" -> "ballerina/io:1.2.0" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.4.0" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/case-description.md new file mode 100644 index 000000000000..c6246d81435e --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/case-description.md @@ -0,0 +1,20 @@ +# Package contains hierarchical imports + +1. User has `ballerina/auth:2.0.0` and the submodule `protobuf.types.empty` of `ballerina/protobuf:0.6.0` in the project where, `ballerina/auth:2.0.0 -> ballerina/io:1.0.1` +2. Current distribution contains `ballerina/cache` versions: `1.2.1`, `1.3.1`, `1.3.2`, `1.4.0` +3. Central repo contains `ballerina/io` versions: `1.0.1`, `1.0.2`, `1.1.0`, `1.2.0` and + `ballerina/auth` versions: `2.0.0` and `2.0.1` where, `ballerina/auth:2.0.1 -> ballerina/cache:1.3.1` +4. `ballerina/protobuf` has versions `0.6.0`, `0.7.0`, `1.6.0`, and `1.7.0` + +## Expected behavior + +### Update policy == SOFT +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.2.0` and `ballerina/cache:1.4.0` should +be newly added as a dependency of `ballerina/auth` +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.0.2` and `ballerina/cache:1.3.2` should +be newly added as a dependency of `ballerina/auth` +### Update policy == HARD +No changes to the graph. `ballerina/protobuf:0.7.0` is considered a breaking change from `ballerina/protobuf:0.6.0` +### Update policy == LOCKED +No changes to the graph. diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-hard.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-hard.dot index 2faa78995af2..ae75d01d842b 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-sticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-hard.dot @@ -1,5 +1,5 @@ digraph "example1" { - "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" "samejs/app:0.1.0" -> "ballerina/protobuf:0.6.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-locked.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-locked.dot new file mode 100644 index 000000000000..ae75d01d842b --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-locked.dot @@ -0,0 +1,5 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" + "samejs/app:0.1.0" -> "ballerina/protobuf:0.6.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-medium.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-medium.dot index 1cb6543d6bd2..947b7a01d48d 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-nosticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-medium.dot @@ -1,6 +1,6 @@ digraph "example1" { - "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" - "ballerina/auth:2.0.1" -> "ballerina/io:1.0.2" "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" "samejs/app:0.1.0" -> "ballerina/protobuf:0.6.0" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" + "ballerina/auth:2.0.1" -> "ballerina/io:1.0.2" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-soft.dot new file mode 100644 index 000000000000..f0cdef5f9477 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0007/expected-graph-soft.dot @@ -0,0 +1,7 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "samejs/app:0.1.0" -> "ballerina/protobuf:0.6.0" + "ballerina/auth:2.0.1" -> "ballerina/io:1.2.0" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.4.0" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/Dependencies_toml.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/Dependencies_toml.dot index 2faa78995af2..ae75d01d842b 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/Dependencies_toml.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/Dependencies_toml.dot @@ -1,5 +1,5 @@ digraph "example1" { - "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" "samejs/app:0.1.0" -> "ballerina/protobuf:0.6.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/case-description.md new file mode 100644 index 000000000000..c445996a2b3f --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/case-description.md @@ -0,0 +1,21 @@ +# Add a new hierarchical import which has a possible package name in the Dependencies.toml + +1. User has `ballerina/auth:2.0.0` and the submodule `protobuf.types.empty` of `ballerina/protobuf:0.6.0` in the project where, `ballerina/auth:2.0.0 -> ballerina/io:1.0.1` +2. Current distribution contains `ballerina/cache` versions: `1.2.1`, `1.3.1`, `1.3.2`, `1.4.0` +3. Central repo contains `ballerina/io` versions: `1.0.1`, `1.0.2`, `1.1.0`, `1.2.0` and + `ballerina/auth` versions: `2.0.0` and `2.0.1` where, `ballerina/auth:2.0.1 -> ballerina/cache:1.3.1` +4. `ballerina/protobuf` has versions `0.6.0`, `0.7.0`, `1.6.0`, and `1.7.0` +5. User adds a new import `ballerina/protobuf.types.timestamp` which possibly can be a submodule of `ballerina/protobuf` + +## Expected behavior + +### Update policy == SOFT +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.2.0`. `ballerina/cache:1.4.0`, +`ballerina/protobuf.types.timestamp:1.0.0` should be added as newly added dependencies. +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.0.2`. `ballerina/cache:1.3.2`, +`ballerina/protobuf.types.timestamp:1.0.0` should be added as newly added dependencies. +### Update policy == HARD +Dependency graph should be updated to add `ballerina/protobuf.types.timestamp:1.0.0` +### Update policy == LOCKED +Build failure since import addition is not allowed in the LOCKED update policy. diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-hard.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-hard.dot diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-medium.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-medium.dot index 34d47a4ceef5..1e1af4226bc3 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-nosticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-medium.dot @@ -1,7 +1,7 @@ digraph "example1" { - "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" - "ballerina/auth:2.0.1" -> "ballerina/io:1.0.2" "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" "samejs/app:0.1.0" -> "ballerina/protobuf:0.6.0" "samejs/app:0.1.0" -> "ballerina/protobuf.types.timestamp:1.0.0" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" + "ballerina/auth:2.0.1" -> "ballerina/io:1.0.2" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-soft.dot new file mode 100644 index 000000000000..b194b0d841d4 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0008/expected-graph-soft.dot @@ -0,0 +1,8 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "samejs/app:0.1.0" -> "ballerina/protobuf:0.6.0" + "samejs/app:0.1.0" -> "ballerina/protobuf.types.timestamp:1.0.0" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.4.0" + "ballerina/auth:2.0.1" -> "ballerina/io:1.2.0" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" +} From 3f03ffd4120c3102089ec224f394631f256b2163 Mon Sep 17 00:00:00 2001 From: Gayal Dassanayake Date: Thu, 16 Jan 2025 07:05:02 +0530 Subject: [PATCH 4/6] Fix 0009-0011 --- .../projects/internal/ResolutionEngine.java | 56 +++++++++++++------ .../projects/internal/index/Index.java | 47 +++++++++++++--- .../projects/internal/index/IndexPackage.java | 35 +++++++++++- .../packages/ExistingProjectTests.java | 24 +++++--- .../case-0009/case-description.md | 18 ++++++ ...aph-sticky.dot => expected-graph-hard.dot} | 2 +- ...nosticky.dot => expected-graph-medium.dot} | 4 +- .../case-0009/expected-graph-soft.dot | 7 +++ .../case-0010/Dependencies_toml.dot | 3 + .../suite-existing_project/case-0010/app.dot | 1 - .../case-0010/case-description.md | 14 +++-- ...aph-sticky.dot => expected-graph-hard.dot} | 6 +- ...nosticky.dot => expected-graph-medium.dot} | 8 +-- .../case-0010/expected-graph-soft.dot | 8 +++ .../suite-existing_project/case-0011/app.dot | 1 - .../case-0011/case-description.md | 12 ++-- .../case-0011/expected-graph-hard.dot | 8 +++ .../case-0011/expected-graph-locked.dot | 8 +++ .../case-0011/expected-graph-medium.dot | 9 +++ .../case-0011/expected-graph-soft.dot | 9 +++ .../repositories/index-local.dot | 5 +- 21 files changed, 225 insertions(+), 60 deletions(-) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/case-description.md rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/{expected-graph-sticky.dot => expected-graph-hard.dot} (100%) rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/{expected-graph-nosticky.dot => expected-graph-medium.dot} (100%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-soft.dot rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/{expected-graph-sticky.dot => expected-graph-hard.dot} (61%) rename compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/{expected-graph-nosticky.dot => expected-graph-medium.dot} (83%) create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-soft.dot create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-hard.dot create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-locked.dot create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-medium.dot create mode 100644 compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-soft.dot diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java index 0418daee7e5e..1023637369c5 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java @@ -183,7 +183,7 @@ private void processUnresolvedNodes( DependencyNode pkgNode = unresolvedNode.dependencyNode(); LockingMode lockingMode = unresolvedNode.lockingMode(); PackageDescriptor pkg = pkgNode.pkgDesc(); - List indexPackageVersions = index.getPackage(pkg.org(), pkg.name()); + List indexPackageVersions = index.getPackage(pkg.org().value(), pkg.name().value()); if (indexPackageVersions == null || indexPackageVersions.isEmpty()) { throw new ProjectException("Package not found in the index: " + pkg); } @@ -222,7 +222,7 @@ private void processUnresolvedNodes( // TODO: refactor and make this method pretty // Consider the repositories, scope etc here. // Filter by deprecated status and the platform as well. - private IndexPackage getLatestCompatibleIndexVersion(List indexPackageVersions, + private IndexPackage getLatestCompatibleIndexVersion(List indexPackages, PackageDescriptor indexRecordedPkg, BlendedManifest.Dependency manifestRecordedPkg, IndexBasedDependencyGraphBuilder graph, @@ -235,8 +235,8 @@ private IndexPackage getLatestCompatibleIndexVersion(List indexPac // If the package is from the local repository, we should pick the exact version. if (manifestRecordedPkg != null && manifestRecordedPkg.isFromLocalRepository()) { // TODO: look at how we should handle the local repos with index. Do we merge the local ones into in memory? - return index.getVersion(manifestRecordedPkg.org(), manifestRecordedPkg.name(), manifestRecordedPkg.version(), "local") - .orElseThrow(() -> new ProjectException("Package not found in the index: " + indexRecordedPkg)); + return index.getVersion(manifestRecordedPkg.org().value(), manifestRecordedPkg.name().value(), manifestRecordedPkg.version(), "local") + .orElseThrow(() -> new ProjectException("Package not found in the local index: " + indexRecordedPkg)); } // if the locking mode is LOCKED, we return the version recorded in the manifest. @@ -244,7 +244,7 @@ private IndexPackage getLatestCompatibleIndexVersion(List indexPac if (manifestRecordedPkg == null) { throw new ProjectException("Cannot have new dependencies with the LOCKED update policy"); } - return index.getVersion(manifestRecordedPkg.org(), manifestRecordedPkg.name(), manifestRecordedPkg.version()) + return index.getVersion(manifestRecordedPkg.org().value(), manifestRecordedPkg.name().value(), manifestRecordedPkg.version()) .orElseThrow(() -> new ProjectException("Package not found in the index: " + indexRecordedPkg)); } @@ -254,7 +254,7 @@ private IndexPackage getLatestCompatibleIndexVersion(List indexPac // compare with the previously fetched value from the index if (indexRecordedPkg.version() != null) { - Optional indexPkg = index.getVersion(indexRecordedPkg.org(), indexRecordedPkg.name(), indexRecordedPkg.version()); + Optional indexPkg = index.getVersion(indexRecordedPkg.org().value(), indexRecordedPkg.name().value(), indexRecordedPkg.version()); if (indexPkg.isEmpty()) { throw new ProjectException("Package not found in the index: " + indexRecordedPkg); } @@ -263,7 +263,7 @@ private IndexPackage getLatestCompatibleIndexVersion(List indexPac // compare with the version recorded in the blended manifest if (manifestRecordedPkg != null) { - Optional manifestIndexPkg = index.getVersion(manifestRecordedPkg.org(), manifestRecordedPkg.name(), manifestRecordedPkg.version()); + Optional manifestIndexPkg = index.getVersion(manifestRecordedPkg.org().value(), manifestRecordedPkg.name().value(), manifestRecordedPkg.version()); if (manifestIndexPkg.isEmpty()) { throw new ProjectException("Package not found in the index: " + manifestRecordedPkg.org() + "/" + manifestRecordedPkg.name() + ":" + manifestRecordedPkg.version()); } @@ -282,7 +282,7 @@ private IndexPackage getLatestCompatibleIndexVersion(List indexPac PackageDescriptor graphRecordedPkg = graph.getDependency(indexRecordedPkg.org(), indexRecordedPkg.name()); if (graphRecordedPkg != null) { - Optional graphIndexPkg = index.getVersion(graphRecordedPkg.org(), graphRecordedPkg.name(), graphRecordedPkg.version()); + Optional graphIndexPkg = index.getVersion(graphRecordedPkg.org().value(), graphRecordedPkg.name().value(), graphRecordedPkg.version()); if (graphIndexPkg.isEmpty()) { throw new ProjectException("Package not found in the index: " + graphRecordedPkg.org() + "/" + graphRecordedPkg.name() + ":" + graphRecordedPkg.version()); } @@ -297,8 +297,24 @@ private IndexPackage getLatestCompatibleIndexVersion(List indexPac candidatePkg = graphIndexPkg; } } - - for (IndexPackage indexPackage : indexPackageVersions) { + List stableIndexPackages = indexPackages.stream().filter(pkg -> !pkg.version().value().isPreReleaseVersion()).toList(); + for (IndexPackage indexPackage : stableIndexPackages) { // TODO: move the loop to another method + // Distribution version check + if (currentBallerinaVersion.major() != indexPackage.ballerinaVersion().major() + || currentBallerinaVersion.minor() < indexPackage.ballerinaVersion().minor()) { + continue; + } + if (candidatePkg.isEmpty() + || isAllowedVersionBump(candidatePkg.get().version(), indexPackage.version(), lockingMode)) { + candidatePkg = Optional.of(indexPackage); + } + } + if (candidatePkg.isPresent()) { + return candidatePkg.get(); + } + // If no matching version found, we rely on pre-release versions + List preReleaseIndexPackages = indexPackages.stream().filter(pkg -> pkg.version().value().isPreReleaseVersion()).toList(); + for (IndexPackage indexPackage : preReleaseIndexPackages) { // Distribution version check if (currentBallerinaVersion.major() != indexPackage.ballerinaVersion().major() || currentBallerinaVersion.minor() < indexPackage.ballerinaVersion().minor()) { @@ -309,10 +325,10 @@ private IndexPackage getLatestCompatibleIndexVersion(List indexPac candidatePkg = Optional.of(indexPackage); } } - if (candidatePkg.isEmpty()) { - throw new ProjectException("No compatible version found in the index for package: " + indexRecordedPkg); + if (candidatePkg.isPresent()) { + return candidatePkg.get(); } - return candidatePkg.get(); + throw new ProjectException("No compatible version found in the index for package: " + indexRecordedPkg); } private boolean areNewDirectDependenciesAdded(Collection directDependencies) { @@ -338,7 +354,8 @@ private boolean isDirectDependencyNewlyAdded(DependencyNode directDependency) { } BlendedManifest.Dependency manifestDep = blendedManifest.dependency( directDependency.pkgDesc().org(), directDependency.pkgDesc().name()).orElseThrow(); - return manifestDep.relation().equals(BlendedManifest.DependencyRelation.TRANSITIVE); + return manifestDep.relation().equals(BlendedManifest.DependencyRelation.TRANSITIVE) || + !manifestDep.version().equals(directDependency.pkgDesc().version()); } private boolean isNewDependency(DependencyNode dependency) { @@ -348,14 +365,11 @@ private boolean isNewDependency(DependencyNode dependency) { } private boolean isAllowedVersionBump( - PackageVersion currentPackageVersion, + PackageVersion currentPackageVersion, // this can be null PackageVersion newPackageVersion, LockingMode lockingMode) { SemanticVersion currentVersion = currentPackageVersion.value(); SemanticVersion newVersion = newPackageVersion.value(); - if (newVersion.isPreReleaseVersion()) { - return false; - } VersionCompatibilityResult compatibility = currentVersion.compareTo(newVersion); return switch (lockingMode) { case LATEST -> compatibility == VersionCompatibilityResult.LESS_THAN @@ -877,4 +891,10 @@ public int compareTo(DependencyNode other) { return this.pkgDesc.toString().compareTo(other.pkgDesc.toString()); } } + + enum VersionOrigin { + DIRECT, + MANIFEST, + INDEX + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java index 8e85043e3567..22289ecb87f8 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java @@ -18,9 +18,8 @@ package io.ballerina.projects.internal.index; -import io.ballerina.projects.PackageName; -import io.ballerina.projects.PackageOrg; import io.ballerina.projects.PackageVersion; +import io.ballerina.projects.SemanticVersion; import java.util.ArrayList; import java.util.HashMap; @@ -30,21 +29,25 @@ import java.util.stream.Stream; // TODO: index should merge the central index and the distribution index. +// TODO: define a set of proper APIs in the index public class Index { - private final Map> packageMap; + private final Map>> packageMap; public static final Index EMPTY_INDEX = new Index(); public Index() { this.packageMap = new HashMap<>(); } - public Optional getVersion(PackageOrg orgName, PackageName packageName, PackageVersion version) { + public Optional getVersion(String orgName, String packageName, PackageVersion version) { return getVersion(orgName, packageName, version, null); } - public Optional getVersion(PackageOrg orgName, PackageName packageName, PackageVersion version, + public Optional getVersion(String orgName, String packageName, PackageVersion version, String repository) { - List packageMap = this.packageMap.get(orgName + "/" + packageName); + if (this.packageMap.get(orgName) == null) { + return Optional.empty(); + } + List packageMap = this.packageMap.get(orgName).get(packageName); if (packageMap == null) { return Optional.empty(); } @@ -57,12 +60,38 @@ public Optional getVersion(PackageOrg orgName, PackageName package public void putVersion(IndexPackage pkg) { List packageMap = this.packageMap.computeIfAbsent( - pkg.org().toString() + "/" + pkg.name().toString(), k -> new ArrayList<>()); + pkg.org().toString(), k -> new HashMap<>()).computeIfAbsent(pkg.name().toString(), k -> new ArrayList<>()); packageMap.add(pkg); } - public List getPackage(PackageOrg orgName, PackageName packageName) { - return packageMap.get(orgName + "/" + packageName); + public List getPackage(String orgName, String packageName) { + if (this.packageMap.get(orgName) == null) { + return new ArrayList<>(); + } + return packageMap.get(orgName).get(packageName); + } + + public List getPackage(String orgName, + String packageName, + String supportedPlatform, + SemanticVersion ballerinaVersion) { + if (this.packageMap.get(orgName) == null) { + return new ArrayList<>(); + } + return packageMap.get(orgName).get(packageName).stream().filter(pkg -> + pkg.supportedPlatform().equals(supportedPlatform) && pkg.ballerinaVersion().equals(ballerinaVersion) + ).toList(); + } + + public List getPackageMatchingModule(String orgName, String moduleName, String ballerinaVersionStr) { + SemanticVersion ballerinaVersion = SemanticVersion.from(ballerinaVersionStr); + if (this.packageMap.get(orgName) == null) { + return new ArrayList<>(); + } + return packageMap.get(orgName).values().stream().flatMap(List::stream) + .filter(pkg -> pkg.modules().stream().anyMatch(module -> module.name().equals(moduleName)) && + ballerinaVersion.greaterThanOrEqualTo(pkg.ballerinaVersion())) + .toList(); } public void putPackages(List pkgs) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java index 6ef9b0d08d67..5aa4de4380cc 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java @@ -31,8 +31,10 @@ public class IndexPackage { private final PackageName packageName; private final PackageVersion packageVersion; private final String repository; + private final String supportedPlatform; private final SemanticVersion ballerinaVersion; private final List dependencies; + private final List modules; // TODO: add other fields as necessary private IndexPackage( @@ -40,24 +42,31 @@ private IndexPackage( PackageName packageName, PackageVersion packageVersion, String repository, + String supportedPlatform, SemanticVersion ballerinaVersion, - List dependencies) { + List dependencies, + List modules) { this.packageOrg = packageOrg; this.packageName = packageName; this.packageVersion = packageVersion; this.repository = repository; + this.supportedPlatform = supportedPlatform; this.ballerinaVersion = ballerinaVersion; this.dependencies = dependencies; + this.modules = modules; } + // TODO: If we pass all data as-it-is, we don't need this from method public static IndexPackage from( PackageOrg packageOrg, PackageName packageName, PackageVersion packageVersion, String repository, + String supportedPlatform, SemanticVersion ballerinaVersion, - List dependencies) { - return new IndexPackage(packageOrg, packageName, packageVersion, repository, ballerinaVersion, dependencies); + List dependencies, + List modules) { + return new IndexPackage(packageOrg, packageName, packageVersion, repository, supportedPlatform, ballerinaVersion, dependencies, modules); } public PackageName name() { @@ -83,4 +92,24 @@ public List dependencies() { public SemanticVersion ballerinaVersion() { return ballerinaVersion; } + + public String supportedPlatform() { + return supportedPlatform; + } + + public List modules() { + return modules; + } + + public static class Module { + String name; + + public Module(String name) { + this.name = name; + } + + public String name() { + return name; + } + } } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java index 1b0c4a7d1192..fa7c1a68a07c 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java @@ -68,15 +68,21 @@ public static Object[][] testCaseProvider() { testCase("case-0008", UpdatePolicy.MEDIUM), testCase("case-0008", UpdatePolicy.HARD), testCase("case-0008", UpdatePolicy.LOCKED, "Invalid state"), -// // 9. Package uses a module available in the newer minor version of an existing dependency -// {"suite-existing_project", "case-0009", true}, -// {"suite-existing_project", "case-0009", false}, -// // 10. package contains dependencies with pre-release versions -// {"suite-existing_project", "case-0010", true}, -// {"suite-existing_project", "case-0010", false}, -// // 11. package contains dependencies with pre-release versions specified from local repo -// {"suite-existing_project", "case-0011", true}, -// {"suite-existing_project", "case-0011", false}, + // 9. Package uses a module available in the newer minor version of an existing dependency + testCase("case-0009", UpdatePolicy.SOFT), + testCase("case-0009", UpdatePolicy.MEDIUM), + testCase("case-0009", UpdatePolicy.HARD), + testCase("case-0009", UpdatePolicy.LOCKED, "Invalid state"), + // 10. package contains dependencies with pre-release versions + testCase("case-0010", UpdatePolicy.SOFT), + testCase("case-0010", UpdatePolicy.MEDIUM), + testCase("case-0010", UpdatePolicy.HARD), + testCase("case-0010", UpdatePolicy.LOCKED, "Invalid state"), + // 11. package contains dependencies with pre-release versions specified from local repo + testCase("case-0011", UpdatePolicy.SOFT), + testCase("case-0011", UpdatePolicy.MEDIUM), + testCase("case-0011", UpdatePolicy.HARD), + testCase("case-0011", UpdatePolicy.LOCKED) // // 12. package contains dependencies which only has pre-release versions published // {"suite-existing_project", "case-0012", true}, // {"suite-existing_project", "case-0012", false}, diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/case-description.md new file mode 100644 index 000000000000..2e1dd6364f3b --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/case-description.md @@ -0,0 +1,18 @@ +# Package uses a module available in the newer minor version of an existing dependency + +1. User has `ballerina/auth:2.0.0` and `ballerina/protobuf:0.6.0` in the project where, `ballerina/auth:2.0.0 -> ballerina/io:1.0.1` +2. `ballerina/protobuf` has versions `0.6.0`, `0.7.0`, `1.6.0`, and `1.7.0` +3. User adds a new import `ballerina/protobuf.types.duration` which possibly can be a submodule of `ballerina/protobuf` + +## Expected behavior + +### Update policy == SOFT +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.2.0`. `ballerina/cache:1.4.0`, +`ballerina/protobuf:1.7.0`. +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.0.2`. `ballerina/cache:1.3.2`, +`ballerina/protobuf:1.7.0`. +### Update policy == HARD +Dependency graph should be updated to have `ballerina/io:1.2.0`. +### Update policy == LOCKED +Build failure since import addition is not allowed in the LOCKED update policy. diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-hard.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-hard.dot index 9eedf28b305b..dc4356a94b07 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-sticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-hard.dot @@ -1,5 +1,5 @@ digraph "example1" { - "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" "samejs/app:0.1.0" -> "ballerina/protobuf:1.7.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-medium.dot similarity index 100% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-medium.dot index 647eb9651c7c..59de6ffd1b93 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-nosticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-medium.dot @@ -1,6 +1,6 @@ digraph "example1" { - "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" - "ballerina/auth:2.0.1" -> "ballerina/io:1.0.2" "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" "samejs/app:0.1.0" -> "ballerina/protobuf:1.7.0" + "ballerina/auth:2.0.1" -> "ballerina/io:1.0.2" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-soft.dot new file mode 100644 index 000000000000..64e1fe109bbe --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0009/expected-graph-soft.dot @@ -0,0 +1,7 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "samejs/app:0.1.0" -> "ballerina/protobuf:1.7.0" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.4.0" + "ballerina/auth:2.0.1" -> "ballerina/io:1.2.0" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/Dependencies_toml.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/Dependencies_toml.dot index cb2ab330c53c..557dca76e6d8 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/Dependencies_toml.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/Dependencies_toml.dot @@ -1,4 +1,7 @@ digraph "example1" { "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" "samejs/app:0.1.0" -> "ballerina/protobuf:1.6.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.0.1" + + "ballerina/io:1.0.1" [transitive = true] } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/app.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/app.dot index 8f0d42ec8433..a3b0a2967742 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/app.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/app.dot @@ -4,5 +4,4 @@ digraph "samejs/app:0.1.0" { "ballerina/io" "ballerina/protobuf" - "ballerina/protobuf.types.duration" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/case-description.md index b7054e4245cc..dbe252ab85f2 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/case-description.md +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/case-description.md @@ -7,8 +7,14 @@ ## Expected behavior -### Sticky == true -No changes to Dependency graph -### Sticky == false -Dependency graph should be updated to have `ballerina/io:1.2.0` +### Update policy == SOFT +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.2.0`. `ballerina/cache:1.4.0`, +`ballerina/protobuf:1.7.0`. +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerina/auth:2.0.1`, `ballerina/io:1.2.0`. `ballerina/cache:1.3.2`. +### Update policy == HARD +Dependency graph should be updated to have `ballerina/protobuf:1.7.0`. +### Update policy == LOCKED +Build failure since import addition is not allowed in the LOCKED update policy. + diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-hard.dot similarity index 61% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-sticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-hard.dot index d8e3f27ca340..715663ac2e56 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-sticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-hard.dot @@ -1,7 +1,7 @@ digraph "example1" { - "ballerina/auth:2.0.0" -> "ballerina/io:1.2.0" - "ballerina/io:1.2.0" -> "ballerina/cache:1.3.2" "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" + "samejs/app:0.1.0" -> "ballerina/protobuf:1.6.0" "samejs/app:0.1.0" -> "ballerina/io:1.2.0" - "samejs/app:0.1.0" -> "ballerina/protobuf:1.7.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.2.0" + "ballerina/io:1.2.0" -> "ballerina/cache:1.3.1" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-medium.dot similarity index 83% rename from compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-nosticky.dot rename to compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-medium.dot index a52e463975a4..9330dc29b81c 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-nosticky.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-medium.dot @@ -1,8 +1,8 @@ digraph "example1" { - "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" - "ballerina/auth:2.0.1" -> "ballerina/io:1.2.0" - "ballerina/io:1.2.0" -> "ballerina/cache:1.3.2" "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "samejs/app:0.1.0" -> "ballerina/protobuf:1.6.0" "samejs/app:0.1.0" -> "ballerina/io:1.2.0" - "samejs/app:0.1.0" -> "ballerina/protobuf:1.7.0" + "ballerina/auth:2.0.1" -> "ballerina/io:1.2.0" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" + "ballerina/io:1.2.0" -> "ballerina/cache:1.3.2" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-soft.dot new file mode 100644 index 000000000000..deff98fd67df --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0010/expected-graph-soft.dot @@ -0,0 +1,8 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "samejs/app:0.1.0" -> "ballerina/io:1.2.0" + "samejs/app:0.1.0" -> "ballerina/protobuf:1.7.0" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.4.0" + "ballerina/auth:2.0.1" -> "ballerina/io:1.2.0" + "ballerina/io:1.2.0" -> "ballerina/cache:1.4.0" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/app.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/app.dot index 8f0d42ec8433..a3b0a2967742 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/app.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/app.dot @@ -4,5 +4,4 @@ digraph "samejs/app:0.1.0" { "ballerina/io" "ballerina/protobuf" - "ballerina/protobuf.types.duration" } diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/case-description.md index 8f9a229da4a5..d2b037656dfe 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/case-description.md +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/case-description.md @@ -4,12 +4,16 @@ 2. A newer pre-release versions `ballerina/io:1.3.0-beta.1` and `ballerina/io:2.0.0-beta.1` has been released to central. `ballerina/io:1.2.0` also has been released to central. 3. User specifies `ballerina/io:1.3.0-beta.2` from local repo in Ballerina.toml -3. User now builds the package +4. User now builds the package ## Expected behavior -### Sticky == true +### Update policy == SOFT Dependency graph should be updated to have `ballerina/io:1.3.0-beta.1` -### Sticky == false +### Update policy == MEDIUM +Dependency graph should be updated to have `ballerina/io:1.3.0-beta.1` +### Update policy == HARD +Dependency graph should be updated to have `ballerina/io:1.3.0-beta.1` +### Update policy == LOCKED +[//]: # (Should this scenario fail?) Dependency graph should be updated to have `ballerina/io:1.3.0-beta.1` - diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-hard.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-hard.dot new file mode 100644 index 000000000000..6c95ae5722e8 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-hard.dot @@ -0,0 +1,8 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" + "samejs/app:0.1.0" -> "ballerina/io:1.3.0-beta.2" + "samejs/app:0.1.0" -> "ballerina/protobuf:1.6.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.3.0-beta.2" + + "ballerina/io:1.3.0-beta.2" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-locked.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-locked.dot new file mode 100644 index 000000000000..6c95ae5722e8 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-locked.dot @@ -0,0 +1,8 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.0" + "samejs/app:0.1.0" -> "ballerina/io:1.3.0-beta.2" + "samejs/app:0.1.0" -> "ballerina/protobuf:1.6.0" + "ballerina/auth:2.0.0" -> "ballerina/io:1.3.0-beta.2" + + "ballerina/io:1.3.0-beta.2" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-medium.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-medium.dot new file mode 100644 index 000000000000..82fcdb6a1a2e --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-medium.dot @@ -0,0 +1,9 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "samejs/app:0.1.0" -> "ballerina/io:1.3.0-beta.2" + "samejs/app:0.1.0" -> "ballerina/protobuf:1.6.0" + "ballerina/auth:2.0.1" -> "ballerina/io:1.3.0-beta.2" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.3.2" + + "ballerina/io:1.3.0-beta.2" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-soft.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-soft.dot new file mode 100644 index 000000000000..5b55d017a8f2 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0011/expected-graph-soft.dot @@ -0,0 +1,9 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "ballerina/auth:2.0.1" + "samejs/app:0.1.0" -> "ballerina/io:1.3.0-beta.2" + "samejs/app:0.1.0" -> "ballerina/protobuf:1.7.0" + "ballerina/auth:2.0.1" -> "ballerina/io:1.3.0-beta.2" + "ballerina/auth:2.0.1" -> "ballerina/cache:1.4.0" + + "ballerina/io:1.3.0-beta.2" [repo = "local"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index-local.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index-local.dot index 9d4b91068e35..be77983668b1 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index-local.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/index-local.dot @@ -1,8 +1,11 @@ -digraph central { +digraph "index-local" { subgraph "samtest/io:1.5.1" { "ballerinaVersion"="2201.0.0"; "samtest/io:1.5.1" -> "ballerina/http:1.3.1" "samtest/io:1.5.1" -> "samjs/c:1.4.5" "samtest/io:1.5.1" -> "samjs/q:1.4.4" } + + subgraph "ballerina/io:1.3.0-beta.2" { + } } \ No newline at end of file From 7dacd2532ba7b9ca26125899a2b2a956d117d587 Mon Sep 17 00:00:00 2001 From: Gayal Dassanayake Date: Mon, 27 Jan 2025 13:50:18 +0530 Subject: [PATCH 5/6] Separate index from the ResolutionEngine --- compiler/ballerina-lang/build.gradle | 4 + .../projects/BuildToolResolution.java | 6 +- .../ballerina/projects/PackageResolution.java | 5 +- .../projects/environment/LockingMode.java | 24 - .../environment/PackageLockingMode.java | 32 +- .../environment/PackageMetadataResponse.java | 23 +- .../environment/PackageRepository.java | 8 +- .../projects/environment/PackageResolver.java | 9 +- .../environment/ResolutionOptions.java | 2 +- .../projects/environment/UpdatePolicy.java | 20 + .../IndexBasedDependencyGraphBuilder.java | 171 ----- .../LockingModeResolutionOptions.java | 81 +++ .../internal/LockingModeResolver.java | 97 ++- .../PackageDependencyGraphBuilder.java | 424 ++---------- .../projects/internal/ResolutionEngine.java | 654 +++--------------- .../projects/internal/UnresolvedNode.java | 24 - .../environment/DefaultPackageResolver.java | 76 +- .../projects/internal/index/Index.java | 102 --- .../projects/internal/index/IndexPackage.java | 87 ++- .../internal/index/IndexPackageAdapter.java | 101 +++ .../projects/internal/index/PackageIndex.java | 115 +++ .../internal/index/PackageIndexBuilder.java | 113 +++ .../internal/index/PackageIndexUpdater.java | 202 ++++++ .../AbstractPackageRepository.java | 31 +- .../repositories/FileSystemRepository.java | 6 +- .../repositories/MavenPackageRepository.java | 7 +- .../repositories/RemotePackageRepository.java | 484 +++++++------ .../projects/util/ProjectConstants.java | 4 + .../ballerina/projects/util/ProjectUtils.java | 32 + .../src/main/java/module-info.java | 2 + .../zip/jballerina-tools/build.gradle | 1 + gradle/libs.versions.toml | 2 + 32 files changed, 1304 insertions(+), 1645 deletions(-) delete mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/LockingMode.java delete mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolutionOptions.java delete mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/UnresolvedNode.java delete mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/Index.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackageAdapter.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndex.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexBuilder.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexUpdater.java diff --git a/compiler/ballerina-lang/build.gradle b/compiler/ballerina-lang/build.gradle index 55f8b87b71ca..bd9e999a0c39 100644 --- a/compiler/ballerina-lang/build.gradle +++ b/compiler/ballerina-lang/build.gradle @@ -36,6 +36,10 @@ dependencies { implementation libs.ow2.asm implementation libs.commons.io implementation libs.zafarkhaja.jsemver + implementation libs.jgit + implementation (libs.guava) { + exclude group: 'com.google.code.findbugs', module: 'jsr305' + } testImplementation(libs.guru.nidi.graphviz) { because("We use this library to execute package resolution tests") } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildToolResolution.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildToolResolution.java index 12c5a832f77e..c6078927b146 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildToolResolution.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildToolResolution.java @@ -209,13 +209,15 @@ private Set getToolResolutionRequests(List unr return resolutionRequests; } + // TODO: we should move tool resolution to the index private ToolResolutionCentralRequest createToolResolutionRequests(Set resolutionRequests) { ToolResolutionCentralRequest toolResolutionRequest = new ToolResolutionCentralRequest(); for (ToolResolutionRequest resolutionRequest : resolutionRequests) { ToolResolutionCentralRequest.Mode mode = switch (resolutionRequest.packageLockingMode()) { - case HARD -> ToolResolutionCentralRequest.Mode.HARD; + case HARD, LOCKED -> ToolResolutionCentralRequest.Mode.HARD; case MEDIUM -> ToolResolutionCentralRequest.Mode.MEDIUM; - case SOFT -> ToolResolutionCentralRequest.Mode.SOFT; + case SOFT, LATEST -> ToolResolutionCentralRequest.Mode.SOFT; + case INVALID -> throw new IllegalStateException("Invalid package locking mode"); }; String version = resolutionRequest.version().map(v -> v.value().toString()).orElse(""); toolResolutionRequest.addTool(resolutionRequest.id().toString(), version, mode); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java index 28901e11cc8f..bc4d73dd0a25 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java @@ -36,7 +36,6 @@ import io.ballerina.projects.internal.ProjectDiagnosticErrorCode; import io.ballerina.projects.internal.ResolutionEngine; import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; -import io.ballerina.projects.internal.index.Index; import io.ballerina.projects.internal.repositories.CustomPkgRepositoryContainer; import io.ballerina.projects.internal.repositories.LocalPackageRepository; import io.ballerina.projects.internal.repositories.MavenPackageRepository; @@ -369,8 +368,8 @@ private DependencyGraph resolveSourceDependencies() { // 2) Resolve imports to packages and create the complete dependency graph with package metadata // TODO: find a better way to pass the new parameters. ResolutionEngine resolutionEngine = new ResolutionEngine(rootPackageContext.descriptor(), - blendedManifest, packageResolver, moduleResolver, resolutionOptions, new Index(), - hasDependencyManifest, distributionChange, lessThan24HrsAfterBuild, false); + blendedManifest, packageResolver, moduleResolver, resolutionOptions, + hasDependencyManifest, distributionChange, lessThan24HrsAfterBuild); DependencyGraph dependencyNodeGraph = resolutionEngine.resolveDependencies(moduleLoadRequests); this.dependencyGraphDump = resolutionEngine.dumpGraphs(); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/LockingMode.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/LockingMode.java deleted file mode 100644 index 1434e5000396..000000000000 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/LockingMode.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you 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.ballerina.projects.environment; - -// TODO: rename this to PackageLockingMode once the transition is properly done -public enum LockingMode { - LATEST, SOFT, MEDIUM, HARD, LOCKED, INVALID -} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageLockingMode.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageLockingMode.java index f1fbe1fb6dfa..7e2dce2eecc7 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageLockingMode.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageLockingMode.java @@ -23,31 +23,29 @@ * @since 2.0.0 */ public enum PackageLockingMode { + /** + * Chooses the latest available version of dependencies. + */ + LATEST, /** * Locks to major versions of dependencies. */ SOFT, /** - * Summary: - * major never - * minor as needed - * patch always - * - * Locks to major versions of dependencies. - * - * For every dependency we always update to latest patch version - * (not conservative about patch versions) - * - * When different dependencies require different minor versions - * of a library, we take the least minor version needed to satisfy - * all requirements - * - * Flag allows upgrade to latest minor version available overriding - * the need question + * Locks to major.minor versions of dependencies. */ MEDIUM, /** * Locks to exact major.minor.patch versions of dependencies. + * However, if there is a compatible higher patch, minor version available in the graphs, use the higher version. + */ + HARD, + /** + * Locks exactly to the previous version of dependencies. + */ + LOCKED, + /** + * The resolution request is invalid. */ - HARD + INVALID } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageMetadataResponse.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageMetadataResponse.java index fd2392950d29..76db5c3ef6ef 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageMetadataResponse.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageMetadataResponse.java @@ -17,10 +17,10 @@ */ package io.ballerina.projects.environment; -import io.ballerina.projects.DependencyGraph; import io.ballerina.projects.PackageDescriptor; -import java.util.Optional; +import java.util.Collection; +import java.util.Collections; /** * {@code PackageMetadataResponse} is used to return a response descriptor to a given {@code ResolutionRequest}. @@ -30,29 +30,32 @@ public class PackageMetadataResponse { private final ResolutionRequest packageLoadRequest; private final PackageDescriptor packageDescriptor; - private final DependencyGraph dependencyGraph; + private final Collection directDependencies; private final ResolutionResponse.ResolutionStatus resolutionStatus; private PackageMetadataResponse(ResolutionRequest packageLoadRequest, PackageDescriptor resolvedDescriptor, - DependencyGraph dependencyGraph, + Collection directDependencies, ResolutionResponse.ResolutionStatus resolutionStatus) { this.packageLoadRequest = packageLoadRequest; this.packageDescriptor = resolvedDescriptor; - this.dependencyGraph = dependencyGraph; + this.directDependencies = directDependencies; this.resolutionStatus = resolutionStatus; } public static PackageMetadataResponse from(ResolutionRequest packageLoadRequest, PackageDescriptor resolvedDescriptor, - DependencyGraph dependencyGraph) { + Collection directDependencies) { return new PackageMetadataResponse( - packageLoadRequest, resolvedDescriptor, dependencyGraph, ResolutionResponse.ResolutionStatus.RESOLVED); + packageLoadRequest, + resolvedDescriptor, + directDependencies, + ResolutionResponse.ResolutionStatus.RESOLVED); } public static PackageMetadataResponse createUnresolvedResponse(ResolutionRequest packageLoadRequest) { return new PackageMetadataResponse( - packageLoadRequest, null, null, ResolutionResponse.ResolutionStatus.UNRESOLVED); + packageLoadRequest, null, Collections.emptyList(), ResolutionResponse.ResolutionStatus.UNRESOLVED); } public PackageDescriptor resolvedDescriptor() { @@ -63,8 +66,8 @@ public ResolutionRequest packageLoadRequest() { return packageLoadRequest; } - public Optional> dependencyGraph() { - return Optional.ofNullable(dependencyGraph); + public Collection directDependencies() { + return directDependencies; } public ResolutionResponse.ResolutionStatus resolutionStatus() { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageRepository.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageRepository.java index 5b80d4f9d51b..c218e83bf933 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageRepository.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageRepository.java @@ -58,16 +58,16 @@ public interface PackageRepository { Map> getPackages(); /** - * Returns requested packages metadata such as the availability, dependency graph. + * Returns requested package metadata such as the availability, direct dependencies. *

* Metadata requests provide an efficient way to complete the dependency graph without * downloading physical packages from Ballerina central. * - * @param requests requested package collection + * @param request requested package collection * @param options resolution options - * @return a collection of {@code PackageMetadataResponse} instances + * @return a {@code PackageMetadataResponse} instance */ - Collection getPackageMetadata(Collection requests, + PackageMetadataResponse getPackageMetadata(ResolutionRequest request, ResolutionOptions options); /** diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageResolver.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageResolver.java index 8ac772fb61ce..e0ec1d2e6e8c 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageResolver.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/PackageResolver.java @@ -41,7 +41,7 @@ Collection resolvePackageNames(Collection * Metadata requests provide an efficient way to complete the dependency graph without * downloading physical packages from Ballerina central. @@ -49,12 +49,11 @@ Collection resolvePackageNames(Collection resolvePackageMetadata(Collection requests, - ResolutionOptions options); + PackageMetadataResponse resolvePackageMetadata(ResolutionRequest request, ResolutionOptions options); /** * Loads the packages specified in {@code ResolutionRequest} collection. diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java index a1199b5f8ba1..9f853cc736f9 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java @@ -96,7 +96,7 @@ public static class ResolutionOptionBuilder { private boolean dumpGraph = false; private boolean dumpRawGraphs = false; private UpdatePolicy updatePolicy = UpdatePolicy.SOFT; - private PackageLockingMode packageLockingMode = PackageLockingMode.MEDIUM; + private PackageLockingMode packageLockingMode = PackageLockingMode.MEDIUM; // TODO: Remove. this isn't a user passed value anymore. Synthesized from the update policy. public ResolutionOptionBuilder setOffline(boolean value) { offline = value; diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/UpdatePolicy.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/UpdatePolicy.java index 888b9bb601ba..8c1a6d16e08c 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/UpdatePolicy.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/UpdatePolicy.java @@ -24,8 +24,28 @@ * @since 2201.12.0 */ public enum UpdatePolicy { + /** + * LATEST update policy allows updating the package to the latest version within the given major version range. + */ SOFT, + + /** + * MEDIUM update policy allows updating the package to the latest version within the given major.minor version range. + */ MEDIUM, + + /** + * HARD update policy sticks to the given major.minor.patch version of the package. However, if there is a + * compatible higher minor/ patch version available in the graphs, use the higher version. + */ HARD, + + /** + * LOCKED update policy sticks to the exact version of the package. This will be not allowed if at least one of the + * following conditions are not met. + * 1. Dependency conflicts are present. + * 2. Dependencies.toml is not available. + * 3. Not all dependencies are locked in the Dependencies.toml. + */ LOCKED } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java deleted file mode 100644 index cc37f5434c12..000000000000 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/IndexBasedDependencyGraphBuilder.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; - -import io.ballerina.projects.DependencyGraph; -import io.ballerina.projects.PackageDescriptor; -import io.ballerina.projects.PackageName; -import io.ballerina.projects.PackageOrg; -import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static io.ballerina.projects.DependencyResolutionType.SOURCE; -import static io.ballerina.projects.PackageDependencyScope.DEFAULT; - -// TODO: Rename this to DependencyGraphBuilder -public class IndexBasedDependencyGraphBuilder { - private final Vertex rootVertex; - private final DependencyNode rootDepNode; - private final Map vertices = new HashMap<>(); - private final Map> depGraph = new HashMap<>(); - - public IndexBasedDependencyGraphBuilder(PackageDescriptor root) { - rootVertex = new Vertex(root.org(), root.name()); - rootDepNode = new DependencyNode(root, DEFAULT, SOURCE); - vertices.put(rootVertex, rootDepNode); - depGraph.put(rootVertex, new HashSet<>()); - } - - public void addDirectDependency(DependencyNode targetNode) { - PackageDescriptor target = targetNode.pkgDesc(); - Vertex targetVertex = new Vertex(target.org(), target.name()); - vertices.put(targetVertex, targetNode); - depGraph.get(rootVertex).add(targetVertex); - depGraph.put(targetVertex, new HashSet<>()); - } - - public void addVertex(DependencyNode node) { - PackageDescriptor pkgDesc = node.pkgDesc(); - Vertex vertex = new Vertex(pkgDesc.org(), pkgDesc.name()); - DependencyNode existingNode = vertices.get(vertex); - if (existingNode != null && existingNode.scope().equals(DEFAULT)) { - node = new DependencyNode(pkgDesc, DEFAULT, node.resolutionType()); - } - vertices.put(vertex, node); - - // if the source node is not in the vertices list, we need to add that to the graph, or - // if the source node version is getting updated, the list of dependencies should be reset and re-added, - // we reset the list of dependencies. - if (existingNode == null || !existingNode.equals(node)) { - depGraph.put(vertex, new HashSet<>()); - } - } - - // TODO: streamline the below logic - public void addDependency(DependencyNode sourceNode, DependencyNode targetNode) { - PackageDescriptor source = sourceNode.pkgDesc(); - PackageDescriptor target = targetNode.pkgDesc(); - Vertex sourceVertex = new Vertex(source.org(), source.name()); - Vertex targetVertex = new Vertex(target.org(), target.name()); - - DependencyNode existingTargetNode = vertices.get(targetVertex); - if (existingTargetNode != null && existingTargetNode.scope().equals(DEFAULT)) { - targetNode = new DependencyNode(target, DEFAULT, targetNode.resolutionType()); - } - vertices.put(targetVertex, targetNode); - if (!depGraph.containsKey(targetVertex)) { - depGraph.put(targetVertex, new HashSet<>()); - } - depGraph.get(sourceVertex).add(targetVertex); - } - - public PackageDescriptor getDependency(PackageOrg org, PackageName name) { - Vertex vertexToFetch = new Vertex(org, name); - DependencyNode node = vertices.get(vertexToFetch); - return node != null? node.pkgDesc() : null; - } - - public DependencyGraph buildGraph() { - removeDanglingNodes(); - DependencyGraph.DependencyGraphBuilder graphBuilder = DependencyGraph.DependencyGraphBuilder.getBuilder(rootDepNode); - for (Map.Entry> dependencyMapEntry : depGraph.entrySet()) { - Vertex graphNodeKey = dependencyMapEntry.getKey(); - Set graphNodeValues = dependencyMapEntry.getValue(); - - DependencyNode pkgDescKey = vertices.get(graphNodeKey); - Set pkgDescValues; - if (graphNodeValues.isEmpty()) { - pkgDescValues = Collections.emptySet(); - } else { - pkgDescValues = new HashSet<>(graphNodeValues.size()); - for (Vertex vertex : graphNodeValues) { - pkgDescValues.add(vertices.get(vertex)); - } - } - graphBuilder.addDependencies(pkgDescKey, pkgDescValues); - } - return graphBuilder.build(); - } - - private void removeDanglingNodes() { - Set danglingVertices = new HashSet<>(vertices.keySet()); - removeDanglingNodes(rootVertex, danglingVertices); - - for (Vertex danglingVertex : danglingVertices) { - vertices.remove(danglingVertex); - depGraph.remove(danglingVertex); - vertices.remove(danglingVertex); - } - } - - private void removeDanglingNodes(Vertex nodeVertex, Set danglingVertices) { - danglingVertices.remove(nodeVertex); - Set dependencies = depGraph.get(nodeVertex); - for (Vertex dep : dependencies) { - if (!danglingVertices.contains(dep)) { - continue; - } - removeDanglingNodes(dep, danglingVertices); - } - } - - public void printGraph() { - System.out.println("Dependency Graph:"); - for (Map.Entry> entry : depGraph.entrySet()) { - System.out.println(entry.getKey() + " -> " + entry.getValue()); - } - } - - /** - * Represents a Vertex in the DAG maintained by the PackageDependencyGraphBuilder. - * - * @since 2.0.0 - */ - private record Vertex(PackageOrg org, PackageName name) { - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - Vertex vertex = (Vertex) o; - return org.equals(vertex.org) && name.equals(vertex.name); - } - } -} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolutionOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolutionOptions.java new file mode 100644 index 000000000000..06626f11642a --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolutionOptions.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; + +import io.ballerina.projects.environment.UpdatePolicy; + +/** + * Represents the resolution options specific for the locking mode. + * + * @since 2201.12.0 + */ +public record LockingModeResolutionOptions(UpdatePolicy updatePolicy, boolean hasDependencyManifest, + boolean distributionChange, boolean importAddition, + boolean lessThan24HrsAfterBuild) { + + /** + * The update policy of the package as passed by the user. + * + * @return the update policy + */ + @Override + public UpdatePolicy updatePolicy() { + return updatePolicy; + } + + /** + * Whether the package has a dependency manifest. + * + * @return true if the package has a dependency manifest, false otherwise + */ + @Override + public boolean hasDependencyManifest() { + return hasDependencyManifest; + } + + /** + * Whether the package is built with a different distribution than the previous build. + * + * @return true if the package has a distribution change, false otherwise + */ + @Override + public boolean distributionChange() { + return distributionChange; + } + + /** + * Whether the package has new imports added. + * + * @return true if the package has new imports added, false otherwise + */ + @Override + public boolean importAddition() { + return importAddition; + } + + /** + * Whether the package is built less than 24 hours after the previous build. + * + * @return true if the package is built less than 24 hours after the previous build, false otherwise + */ + @Override + public boolean lessThan24HrsAfterBuild() { + return lessThan24HrsAfterBuild; + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java index e2092c141c56..6373d1c1b60f 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java @@ -18,92 +18,89 @@ package io.ballerina.projects.internal; -import io.ballerina.projects.environment.LockingMode; -import io.ballerina.projects.environment.UpdatePolicy; +import io.ballerina.projects.environment.PackageLockingMode; +/** + * This class resolves the locking modes for the packages based on the update policy and the package status. + * + * @since 2201.12.0 + */ public class LockingModeResolver { - // TODO: may be we can encapsulate these fields in a new class LockingModeResolutionOptions - private final UpdatePolicy updatePolicy; - private boolean hasDependencyManifest; - private final boolean distributionChange; - private final boolean importAddition; - private final boolean lessThan24HrsAfterBuild; + private final LockingModeResolutionOptions options; - public LockingModeResolver( - UpdatePolicy updatePolicy, - boolean hasDependencyManifest, - boolean distributionChange, - boolean importAddition, - boolean lessThan24HrsAfterBuild - ) { - this.updatePolicy = updatePolicy; - this.hasDependencyManifest = hasDependencyManifest; - this.distributionChange = distributionChange; - this.importAddition = importAddition; - this.lessThan24HrsAfterBuild = lessThan24HrsAfterBuild; + public LockingModeResolver(LockingModeResolutionOptions options) { + this.options = options; } + /** + * Resolves the locking modes for the package based on the update policy and the package status. + * + * @return the resolved locking modes + */ public LockingModes resolveLockingModes() { - if (!hasDependencyManifest) { + if (!options.hasDependencyManifest()) { return resolveNoManifestLockingMode(); } - if (distributionChange) { + if (options.distributionChange()) { return resolveDistributionChangeLockingMode(); } - if (importAddition) { + if (options.importAddition()) { return resolveImportAdditionLockingMode(); } - if (lessThan24HrsAfterBuild) { - return new LockingModes(LockingMode.LOCKED); + if (options.lessThan24HrsAfterBuild()) { + return new LockingModes(PackageLockingMode.LOCKED); } return resolveDefaultLockingMode(); } public record LockingModes( - LockingMode existingDirectDepMode, - LockingMode existingTransitiveDepMode, - LockingMode newDirectDepMode, - LockingMode newTransitiveDepMode) { - public LockingModes(LockingMode existingDirectDepMode, LockingMode existingTransDepMode) { + PackageLockingMode existingDirectDepMode, + PackageLockingMode existingTransitiveDepMode, + PackageLockingMode newDirectDepMode, + PackageLockingMode newTransitiveDepMode) { + public LockingModes(PackageLockingMode existingDirectDepMode, PackageLockingMode existingTransDepMode) { this(existingDirectDepMode, existingTransDepMode, existingDirectDepMode, existingTransDepMode); } - public LockingModes(LockingMode mode) { + public LockingModes(PackageLockingMode mode) { this(mode, mode, mode, mode); } } private LockingModes resolveNoManifestLockingMode() { - return switch (updatePolicy) { - case SOFT -> new LockingModes(LockingMode.LATEST, LockingMode.SOFT); - case MEDIUM -> new LockingModes(LockingMode.LATEST, LockingMode.MEDIUM); - case HARD -> new LockingModes(LockingMode.LATEST, LockingMode.HARD); - default -> new LockingModes(LockingMode.INVALID); + return switch (options.updatePolicy()) { + case SOFT -> new LockingModes(PackageLockingMode.LATEST, PackageLockingMode.SOFT); + case MEDIUM -> new LockingModes(PackageLockingMode.LATEST, PackageLockingMode.MEDIUM); + case HARD -> new LockingModes(PackageLockingMode.LATEST, PackageLockingMode.HARD); + case LOCKED -> new LockingModes(PackageLockingMode.INVALID); }; } private LockingModes resolveDistributionChangeLockingMode() { - return switch (updatePolicy) { - case SOFT -> new LockingModes(LockingMode.SOFT); - case MEDIUM, HARD -> new LockingModes(LockingMode.MEDIUM); - case LOCKED -> new LockingModes(LockingMode.LOCKED); + return switch (options.updatePolicy()) { + case SOFT -> new LockingModes(PackageLockingMode.SOFT); + case MEDIUM, HARD -> new LockingModes(PackageLockingMode.MEDIUM); + case LOCKED -> new LockingModes(PackageLockingMode.LOCKED); }; } private LockingModes resolveImportAdditionLockingMode() { - return switch (updatePolicy) { - case SOFT -> new LockingModes(LockingMode.SOFT, LockingMode.SOFT, LockingMode.LATEST, LockingMode.SOFT); - case MEDIUM -> new LockingModes(LockingMode.MEDIUM, LockingMode.MEDIUM, LockingMode.LATEST, LockingMode.MEDIUM); - case HARD -> new LockingModes(LockingMode.HARD, LockingMode.HARD, LockingMode.LATEST, LockingMode.HARD); - case LOCKED -> new LockingModes(LockingMode.INVALID); + return switch (options.updatePolicy()) { + case SOFT -> new LockingModes(PackageLockingMode.SOFT, PackageLockingMode.SOFT, + PackageLockingMode.LATEST, PackageLockingMode.SOFT); + case MEDIUM -> new LockingModes(PackageLockingMode.MEDIUM, PackageLockingMode.MEDIUM, + PackageLockingMode.LATEST, PackageLockingMode.MEDIUM); + case HARD -> new LockingModes(PackageLockingMode.HARD, PackageLockingMode.HARD, + PackageLockingMode.LATEST, PackageLockingMode.HARD); + case LOCKED -> new LockingModes(PackageLockingMode.INVALID); }; } private LockingModes resolveDefaultLockingMode() { - return switch (updatePolicy) { - case SOFT -> new LockingModes(LockingMode.SOFT); - case MEDIUM -> new LockingModes(LockingMode.MEDIUM); - case HARD -> new LockingModes(LockingMode.HARD); - default -> new LockingModes(LockingMode.LOCKED); + return switch (options.updatePolicy()) { + case SOFT -> new LockingModes(PackageLockingMode.SOFT); + case MEDIUM -> new LockingModes(PackageLockingMode.MEDIUM); + case HARD -> new LockingModes(PackageLockingMode.HARD); + case LOCKED -> new LockingModes(PackageLockingMode.LOCKED); }; } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageDependencyGraphBuilder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageDependencyGraphBuilder.java index 5677f676cbe9..f5bdad232247 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageDependencyGraphBuilder.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageDependencyGraphBuilder.java @@ -15,35 +15,26 @@ * specific language governing permissions and limitations * under the License. */ + package io.ballerina.projects.internal; import io.ballerina.projects.DependencyGraph; -import io.ballerina.projects.DependencyGraph.DependencyGraphBuilder; -import io.ballerina.projects.DependencyResolutionType; -import io.ballerina.projects.PackageDependencyScope; import io.ballerina.projects.PackageDescriptor; import io.ballerina.projects.PackageName; import io.ballerina.projects.PackageOrg; -import io.ballerina.projects.SemanticVersion; -import io.ballerina.projects.SemanticVersion.VersionCompatibilityResult; -import io.ballerina.projects.environment.ResolutionOptions; import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; -import io.ballerina.projects.util.ProjectConstants; -import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.diagnostics.DiagnosticInfo; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; -import java.util.ArrayList; -import java.util.Collection; +import java.io.PrintStream; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; +import static io.ballerina.projects.DependencyResolutionType.SOURCE; +import static io.ballerina.projects.PackageDependencyScope.DEFAULT; + /** * This class is responsible for creating the Package dependency graph with no version conflicts. *

@@ -52,106 +43,71 @@ * @since 2.0.0 */ public class PackageDependencyGraphBuilder { - // TODO how about a multi-level map here Map> + private final Vertex rootVertex; + private final DependencyNode rootDepNode; private final Map vertices = new HashMap<>(); private final Map> depGraph = new HashMap<>(); + private final PrintStream outStream = System.out; - // We are maintaining the raw graph for trouble shooting purposes. - private final DependencyGraphBuilder rawGraphBuilder; - private final ResolutionOptions resolutionOptions; - - private Set unresolvedVertices = new HashSet<>(); - - private final DependencyNode rootDepNode; - private final Vertex rootNodeVertex; - - private final List diagnosticList; - - public PackageDependencyGraphBuilder(PackageDescriptor rootNode, ResolutionOptions resolutionOptions) { - diagnosticList = new ArrayList<>(); - this.rootNodeVertex = new Vertex(rootNode.org(), rootNode.name()); - this.rootDepNode = new DependencyNode(rootNode, - PackageDependencyScope.DEFAULT, DependencyResolutionType.SOURCE); - this.resolutionOptions = resolutionOptions; - this.rawGraphBuilder = DependencyGraphBuilder.getBuilder(rootDepNode); - - Vertex dependentVertex = new Vertex(rootDepNode.pkgDesc().org(), rootDepNode.pkgDesc().name()); - addNewVertex(dependentVertex, rootDepNode, false); - } - - public NodeStatus addUnresolvedNode(PackageDescriptor node, - PackageDependencyScope scope, - DependencyResolutionType dependencyResolvedType) { - // Add the correct version of the dependent to the graph. - Vertex dependentVertex = new Vertex(node.org(), node.name()); - return addNewVertex(dependentVertex, - new DependencyNode(node, scope, dependencyResolvedType), true); + public PackageDependencyGraphBuilder(PackageDescriptor root) { + rootVertex = new Vertex(root.org(), root.name()); + rootDepNode = new DependencyNode(root, DEFAULT, SOURCE); + vertices.put(rootVertex, rootDepNode); + depGraph.put(rootVertex, new HashSet<>()); } - public NodeStatus addResolvedNode(PackageDescriptor node, - PackageDependencyScope scope, - DependencyResolutionType dependencyResolvedType) { - // Add the correct version of the dependent to the graph. - Vertex dependentVertex = new Vertex(node.org(), node.name()); - return addNewVertex(dependentVertex, - new DependencyNode(node, scope, dependencyResolvedType), false); + public void addDirectDependency(DependencyNode targetNode) { + PackageDescriptor target = targetNode.pkgDesc(); + Vertex targetVertex = new Vertex(target.org(), target.name()); + vertices.put(targetVertex, targetNode); + depGraph.get(rootVertex).add(targetVertex); + depGraph.put(targetVertex, new HashSet<>()); } - public NodeStatus addErrorNode(PackageDescriptor node, - PackageDependencyScope scope, - DependencyResolutionType dependencyResolvedType) { - // Add the correct version of the dependent to the graph. - Vertex dependentVertex = new Vertex(node.org(), node.name()); - return addNewVertex(dependentVertex, - new DependencyNode(node, scope, dependencyResolvedType, true), false); - } - - public NodeStatus addUnresolvedDependency(PackageDescriptor dependent, - PackageDescriptor dependency, - PackageDependencyScope dependencyScope, - DependencyResolutionType dependencyResolvedType) { - return addDependencyInternal(dependent, - new DependencyNode(dependency, dependencyScope, dependencyResolvedType), - true); - } + public void addVertex(DependencyNode node) { + PackageDescriptor pkgDesc = node.pkgDesc(); + Vertex vertex = new Vertex(pkgDesc.org(), pkgDesc.name()); + DependencyNode existingNode = vertices.get(vertex); + if (existingNode != null && existingNode.scope().equals(DEFAULT)) { + node = new DependencyNode(pkgDesc, DEFAULT, node.resolutionType()); + } + vertices.put(vertex, node); - public NodeStatus addErroneousDependency(PackageDescriptor dependent, - PackageDescriptor dependency, - PackageDependencyScope dependencyScope, - DependencyResolutionType dependencyResolvedType) { - return addDependencyInternal(dependent, - new DependencyNode(dependency, dependencyScope, dependencyResolvedType, true), - false); + // if the source node is not in the vertices list, we need to add that to the graph, or + // if the source node version is getting updated, the list of dependencies should be reset and re-added, + // we reset the list of dependencies. + if (existingNode == null || !existingNode.equals(node)) { + depGraph.put(vertex, new HashSet<>()); + } } - public NodeStatus addResolvedDependency(PackageDescriptor dependent, - PackageDescriptor dependency, - PackageDependencyScope dependencyScope, - DependencyResolutionType dependencyResolvedType) { - return addDependencyInternal(dependent, - new DependencyNode(dependency, dependencyScope, dependencyResolvedType), - false); - } + public void addDependency(DependencyNode sourceNode, DependencyNode targetNode) { + PackageDescriptor source = sourceNode.pkgDesc(); + PackageDescriptor target = targetNode.pkgDesc(); + Vertex sourceVertex = new Vertex(source.org(), source.name()); + Vertex targetVertex = new Vertex(target.org(), target.name()); - public boolean containsNode(PackageDescriptor node) { - DependencyNode dependencyNode = vertices.get(new Vertex(node.org(), node.name())); - if (dependencyNode == null) { - return false; + DependencyNode existingTargetNode = vertices.get(targetVertex); + if (existingTargetNode != null && existingTargetNode.scope().equals(DEFAULT)) { + targetNode = new DependencyNode(target, DEFAULT, targetNode.resolutionType()); + } + vertices.put(targetVertex, targetNode); + if (!depGraph.containsKey(targetVertex)) { + depGraph.put(targetVertex, new HashSet<>()); } - return dependencyNode.pkgDesc().version().equals(node.version()); + depGraph.get(sourceVertex).add(targetVertex); } - public Collection getAllDependencies() { - return vertices.values().stream() - .filter(vertex -> !vertex.equals(rootDepNode)) - .toList(); + public PackageDescriptor getDependency(PackageOrg org, PackageName name) { + Vertex vertexToFetch = new Vertex(org, name); + DependencyNode node = vertices.get(vertexToFetch); + return node != null ? node.pkgDesc() : null; } public DependencyGraph buildGraph() { - // Remove dangling nodes in the graph removeDanglingNodes(); - - DependencyGraphBuilder graphBuilder = DependencyGraphBuilder.getBuilder(rootDepNode); + DependencyGraph.DependencyGraphBuilder graphBuilder = DependencyGraph + .DependencyGraphBuilder.getBuilder(rootDepNode); for (Map.Entry> dependencyMapEntry : depGraph.entrySet()) { Vertex graphNodeKey = dependencyMapEntry.getKey(); Set graphNodeValues = dependencyMapEntry.getValue(); @@ -166,65 +122,20 @@ public DependencyGraph buildGraph() { pkgDescValues.add(vertices.get(vertex)); } } - graphBuilder.addDependencies(pkgDescKey, pkgDescValues); } return graphBuilder.build(); } - public Collection getUnresolvedNodes() { - Collection unresolvedNodes = unresolvedVertices.stream() - .map(vertices::get) - .toList(); - this.unresolvedVertices = new HashSet<>(); - return unresolvedNodes; - } - - List diagnostics() { - return diagnosticList; - } - - /** - * Clean up the dependency graph by cleaning up dangling dependencies. - */ - public void removeDanglingNodes() { + private void removeDanglingNodes() { Set danglingVertices = new HashSet<>(vertices.keySet()); - removeDanglingNodes(rootNodeVertex, danglingVertices); + removeDanglingNodes(rootVertex, danglingVertices); for (Vertex danglingVertex : danglingVertices) { vertices.remove(danglingVertex); depGraph.remove(danglingVertex); - unresolvedVertices.remove(danglingVertex); - } - } - - public DependencyGraph rawGraph() { - return rawGraphBuilder.build(); - } - - void addUnresolvedDirectDepToRawGraph(DependencyNode unresolvedDirectDep) { - rawGraphBuilder.addDependency(this.rootDepNode, unresolvedDirectDep); - } - - private NodeStatus addDependencyInternal(PackageDescriptor dependent, - DependencyNode dependencyNode, - boolean unresolved) { - // Add the correct version of the dependent to the graph. - Vertex dependentVertex = new Vertex(dependent.org(), dependent.name()); - if (!depGraph.containsKey(dependentVertex)) { - throw new IllegalStateException("Dependent node does not exist in the graph: " + dependent); - } - - Vertex dependencyVertex = new Vertex(dependencyNode.pkgDesc().org(), dependencyNode.pkgDesc().name()); - NodeStatus nodeStatus = addNewVertex(dependencyVertex, dependencyNode, unresolved); - depGraph.get(dependentVertex).add(dependencyVertex); - - // Recording every dependency relation in raw graph for troubleshooting purposes. - if (resolutionOptions.dumpRawGraphs()) { - DependencyNode dependentNode = vertices.get(dependentVertex); - rawGraphBuilder.addDependency(dependentNode, dependencyNode); + vertices.remove(danglingVertex); } - return nodeStatus; } private void removeDanglingNodes(Vertex nodeVertex, Set danglingVertices) { @@ -239,206 +150,23 @@ private void removeDanglingNodes(Vertex nodeVertex, Set danglingVertices } /** - * Adds a new vertex if it is not added to the graph yet. - * Replaces/rejects the new vertex by comparing with the existing node - * in the dependency graph. When accepting or rejecting the new vertex the - * following rules are followed: - * 1. If the existing node is an erroneous node, reject the new node. - * 2. If the new node is an erroneous node, accept it. - * 3. If there new node version is incompatible with the existing, - * create an error node and add to the graph. - * 4. If the new node version is greater than the existing, accept it. Else reject. - * - * @param vertex existing vertex in the graph - * @param newPkgDep new node to compare - * @param unresolved whether the vertex is unresolved - * @return + * Print the dependency graph. */ - private NodeStatus addNewVertex(Vertex vertex, DependencyNode newPkgDep, boolean unresolved) { - // Adding every node to the raw graph - if (resolutionOptions.dumpRawGraphs()) { - rawGraphBuilder.add(newPkgDep); - } - - if (!vertices.containsKey(vertex)) { - vertices.put(vertex, newPkgDep); - depGraph.put(vertex, new HashSet<>()); - - // TODO Re think about the unresolved Property - if (unresolved) { - unresolvedVertices.add(vertex); - } - return NodeStatus.ACCEPTED; - } - - // There exists another version in the graph. - DependencyNode existingPkgDep = vertices.get(vertex); - - // If the existing dependency is an error node, we continue with the error node. - if (existingPkgDep.errorNode()) { - return NodeStatus.REJECTED; - } - - DependencyNode resolvedPkgDep; - NodeStatus nodeStatus; - - if (newPkgDep.errorNode()) { - resolvedPkgDep = newPkgDep; - nodeStatus = NodeStatus.ACCEPTED; - } else { - PackageDescriptor resolvedPkgDesc = handleDependencyConflict(newPkgDep, existingPkgDep); - if (resolvedPkgDesc == null) { // A version conflict exists. We add the a new error node. - resolvedPkgDep = new DependencyNode( - existingPkgDep.pkgDesc(), - existingPkgDep.scope(), - existingPkgDep.resolutionType(), - true); - nodeStatus = NodeStatus.ACCEPTED; - } else { - // If the existing dependency scope is DEFAULT, use it. Otherwise use the new dependency scope. - PackageDependencyScope depScope = - existingPkgDep.scope() == PackageDependencyScope.DEFAULT ? - PackageDependencyScope.DEFAULT : - newPkgDep.scope(); - - // If the existing dependency scope is DEFAULT, use it. Otherwise use the new dependency scope. - DependencyResolutionType resolutionType = - existingPkgDep.resolutionType() == DependencyResolutionType.SOURCE ? - DependencyResolutionType.SOURCE : - newPkgDep.resolutionType(); - - resolvedPkgDep = new DependencyNode(resolvedPkgDesc, depScope, resolutionType); - nodeStatus = getNodeStatus(vertex, existingPkgDep, newPkgDep, unresolved); - } - } - - // Accept or reject the new package dependency depending on the node status - if (nodeStatus == NodeStatus.ACCEPTED) { - // Update the vertex with the new version - vertices.put(vertex, resolvedPkgDep); - // The dependencies of the current version is no longer valid. - depGraph.put(vertex, new HashSet<>()); - } else { - // Update the vertex anyway - // This step will update the correct scope, resolution type and repository - vertices.put(vertex, resolvedPkgDep); - if (resolutionOptions.dumpRawGraphs()) { - rawGraphBuilder.add(resolvedPkgDep); - } - // Update the scope of dependencies only if rejected node scope is DEFAULT - if (resolvedPkgDep.scope() == PackageDependencyScope.DEFAULT) { - for (Vertex depVertex : depGraph.get(vertex)) { - DependencyNode dependencyNode = vertices.get(depVertex); - DependencyNode newDependencyNode = new DependencyNode(dependencyNode.pkgDesc(), - resolvedPkgDep.scope(), dependencyNode.resolutionType()); - vertices.put(depVertex, newDependencyNode); - if (resolutionOptions.dumpRawGraphs()) { - rawGraphBuilder.addDependency(resolvedPkgDep, newDependencyNode); - } - } - } - } - return nodeStatus; - } - - private NodeStatus getNodeStatus(Vertex vertex, - DependencyNode existingPkgDep, - DependencyNode newPkgDep, - boolean unresolved) { - if (newPkgDep.equals(existingPkgDep)) { - return NodeStatus.ACCEPTED; - } - - SemanticVersion newSemVer = newPkgDep.pkgDesc().version().value(); - SemanticVersion existingSemVer = existingPkgDep.pkgDesc().version().value(); - if (newSemVer.greaterThan(existingSemVer)) { - if (unresolved) { - // Mark it as unresolved because there is version change - unresolvedVertices.add(vertex); - } - return NodeStatus.ACCEPTED; - } else if (newSemVer.lessThan(existingSemVer)) { - return NodeStatus.REJECTED; - } else { - // We have the same package version in existing dependency and in the resolved dependency - // Let's compare the package repository - Optional newRepoOptional = newPkgDep.pkgDesc().repository(); - Optional existingRepoOptional = existingPkgDep.pkgDesc().repository(); - if (newRepoOptional.isPresent() && - newRepoOptional.get().equals(ProjectConstants.LOCAL_REPOSITORY_NAME)) { - // New package is coming from the local repository. Accept the new one - return NodeStatus.ACCEPTED; - } - - if (existingRepoOptional.isPresent() && - existingRepoOptional.get().equals(ProjectConstants.LOCAL_REPOSITORY_NAME)) { - // New package is not coming from the local repository and the - // existing package is coming from the local repository. Reject the new one - return NodeStatus.REJECTED; - } - - // At this point, - // Both versions are same and both of them are not coming from the local repository - // But scope and/or the resolution type is different in the resolved version. Accept it. - return NodeStatus.ACCEPTED; - } - } - - private PackageDescriptor handleDependencyConflict(DependencyNode newPkgDep, - DependencyNode existingPkgDep) { - PackageDescriptor newPkgDesc = newPkgDep.pkgDesc(); - PackageDescriptor existingPkgDesc = existingPkgDep.pkgDesc(); - - VersionCompatibilityResult compatibilityResult = newPkgDesc.version().compareTo(existingPkgDesc.version()); - switch (compatibilityResult) { - case EQUAL: - // Both packages have the same version - // Give priority to the package coming from the local repository - String repository = existingPkgDesc.repository().isPresent() ? - existingPkgDesc.repository().get() : - newPkgDesc.repository().orElse(null); - return PackageDescriptor.from(existingPkgDesc.org(), existingPkgDesc.name(), - existingPkgDesc.version(), repository); - case LESS_THAN: - // New dependency version is less than the existing version - // Use the existing package - return existingPkgDesc; - case GREATER_THAN: - // New dependency version is greater than the existing version - // Use the existing package - return newPkgDesc; - case INCOMPATIBLE: - // Incompatible versions exist in the graph. - // TODO can we report this issue with more information. dependency graph etc. - // Convert this to a diagnostic - DiagnosticInfo diagnosticInfo = new DiagnosticInfo( - ProjectDiagnosticErrorCode.INCOMPATIBLE_DEPENDENCY_VERSIONS.diagnosticId(), - "Two incompatible versions exist in the dependency graph: " + - existingPkgDesc.org() + "/" + existingPkgDesc.name() + - " versions: " + existingPkgDesc.version() + ", " + newPkgDesc.version(), - DiagnosticSeverity.ERROR); - PackageDiagnostic diagnostic = new PackageDiagnostic( - diagnosticInfo, this.rootDepNode.pkgDesc().name().toString()); - diagnosticList.add(diagnostic); - return null; - default: - throw new IllegalStateException("Unsupported VersionCompatibilityResult: " + compatibilityResult); + public void printGraph() { + outStream.println("Dependency Graph:"); + for (Map.Entry> entry : depGraph.entrySet()) { + outStream.println(entry.getKey() + " -> " + entry.getValue()); } } /** * Represents a Vertex in the DAG maintained by the PackageDependencyGraphBuilder. + * @param org organization + * @param name package name * * @since 2.0.0 */ - private static class Vertex { - private final PackageOrg org; - private final PackageName name; - - Vertex(PackageOrg org, PackageName name) { - this.org = org; - this.name = name; - } + private record Vertex(PackageOrg org, PackageName name) { @Override public boolean equals(Object o) { @@ -459,30 +187,4 @@ public int hashCode() { return Objects.hash(org, name); } } - - /** - * Indicates whether a node is added to the graph or not. - * - * @since 2.0.0 - */ - public enum NodeStatus { - /** - * Indicates that the node is added to the graph. - * A node is accepted if: - * 1) The graph does not new version, - * 2) The new version is greater than the current version in the graph, - * 3) Both versions are the same and the new version is coming from the local repo, - * 4) Both versions are the same and both versions are not coming from the local repo, - */ - ACCEPTED, - - /** - * Indicates that the node is not added to the graph. - * A node is rejected if: - * 1) The new version is lower than the current version in the graph, - * 2) Both versions are the same and the existing version is coming from the local repo - * whereas the new version is not - */ - REJECTED - } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java index 1023637369c5..a77137c9d3f8 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java @@ -26,7 +26,6 @@ import io.ballerina.projects.ProjectException; import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.SemanticVersion.VersionCompatibilityResult; -import io.ballerina.projects.environment.LockingMode; import io.ballerina.projects.environment.ModuleLoadRequest; import io.ballerina.projects.environment.PackageLockingMode; import io.ballerina.projects.environment.PackageMetadataResponse; @@ -34,20 +33,10 @@ import io.ballerina.projects.environment.ResolutionOptions; import io.ballerina.projects.environment.ResolutionRequest; import io.ballerina.projects.environment.ResolutionResponse; -import io.ballerina.projects.internal.PackageDependencyGraphBuilder.NodeStatus; -import io.ballerina.projects.internal.index.Index; -import io.ballerina.projects.internal.index.IndexDependency; -import io.ballerina.projects.internal.index.IndexPackage; -import io.ballerina.projects.util.ProjectConstants; import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.diagnostics.DiagnosticInfo; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; -import org.wso2.ballerinalang.util.RepoUtils; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -71,41 +60,29 @@ public class ResolutionEngine { private String dependencyGraphDump; private DiagnosticResult diagnosticResult; private Set unresolvedDeps = null; - private final Index index; boolean hasDependencyManifest; boolean distributionChange; boolean lessThan24HrsAfterBuild; - private final boolean indexTest; public ResolutionEngine(PackageDescriptor rootPkgDesc, BlendedManifest blendedManifest, PackageResolver packageResolver, ModuleResolver moduleResolver, ResolutionOptions resolutionOptions, - Index index, boolean hasDependencyManifest, boolean distributionChange, - boolean lessThan24HrsAfterBuild, - boolean indexTest) { + boolean lessThan24HrsAfterBuild) { this.rootPkgDesc = rootPkgDesc; this.blendedManifest = blendedManifest; this.packageResolver = packageResolver; this.moduleResolver = moduleResolver; this.resolutionOptions = resolutionOptions; - - this.graphBuilder = new PackageDependencyGraphBuilder(rootPkgDesc, resolutionOptions); + this.graphBuilder = new PackageDependencyGraphBuilder(rootPkgDesc); this.diagnostics = new ArrayList<>(); this.dependencyGraphDump = ""; - this.index = index; this.hasDependencyManifest = hasDependencyManifest; - this.distributionChange = distributionChange; - this.lessThan24HrsAfterBuild = lessThan24HrsAfterBuild; - this.indexTest = indexTest; - } - - // TODO: remove this and have the logic in the constructor once this is finalized. - public void populateIndex(List packageDescriptors) { - index.putPackages(packageDescriptors); + this.distributionChange = distributionChange; // TODO move these into a single object + this.lessThan24HrsAfterBuild = lessThan24HrsAfterBuild; // TODO move these into a single object } public DiagnosticResult diagnosticResult() { @@ -119,31 +96,8 @@ public DependencyGraph resolveDependencies(Collection directDependencies = resolvePackages(moduleLoadRequests); - if (indexTest) { - return resolveDependenciesWithIndex(directDependencies); - } - // 2) Create the static/initial dependency graph. - // This graph contains direct dependencies and their transitives, - // but we don't update versions. - populateStaticDependencyGraph(directDependencies); - - // 3) Update the dependency versions if required - // This method traverse through the graph as many time as time until the graph is completed. - // Graph is complete when it contains latest compatible versions of all dependencies. - updateDependencyVersions(); - - // 4) Now the first round of update is done, but there may be more unresolved nodes in the graph builder. - // We need to keep resolving the unresolved nodes until the graph is complete. - completeDependencyGraph(); - - // 5) Build final the dependency graph. - return buildFinalDependencyGraph(); - } - - private DependencyGraph resolveDependenciesWithIndex(Collection directDependencies) { LockingModeResolver.LockingModes lockingModes = getLockingModes(directDependencies); - // TODO: look at how to handle the blended manifest versions - IndexBasedDependencyGraphBuilder graphBuilder = new IndexBasedDependencyGraphBuilder(rootPkgDesc); + PackageDependencyGraphBuilder graphBuilder = new PackageDependencyGraphBuilder(rootPkgDesc); Queue unresolvedNodes = new LinkedList<>(); initializeDirectDependencies(directDependencies, lockingModes, graphBuilder, unresolvedNodes); processUnresolvedNodes(lockingModes, graphBuilder, unresolvedNodes); @@ -152,22 +106,23 @@ private DependencyGraph resolveDependenciesWithIndex(Collection< private LockingModeResolver.LockingModes getLockingModes(Collection directDependencies) { boolean importAddition = areNewDirectDependenciesAdded(directDependencies); - LockingModeResolver lockingModeResolver = new LockingModeResolver( + LockingModeResolutionOptions options = new LockingModeResolutionOptions( resolutionOptions.updatePolicy(), hasDependencyManifest, distributionChange, importAddition, lessThan24HrsAfterBuild); + LockingModeResolver lockingModeResolver = new LockingModeResolver(options); return lockingModeResolver.resolveLockingModes(); } private void initializeDirectDependencies( Collection directDependencies, LockingModeResolver.LockingModes lockingModeMap, - IndexBasedDependencyGraphBuilder graphBuilder, + PackageDependencyGraphBuilder graphBuilder, Queue unresolvedNodes) { for (DependencyNode directDependency : directDependencies) { - LockingMode directDepLockingMode = isDirectDependencyNewlyAdded(directDependency)? + PackageLockingMode directDepLockingMode = isDirectDependencyNewlyAdded(directDependency) ? lockingModeMap.newDirectDepMode() : lockingModeMap.existingDirectDepMode(); graphBuilder.addDirectDependency(directDependency); unresolvedNodes.add(new UnresolvedNode(directDependency, directDepLockingMode)); @@ -176,29 +131,52 @@ private void initializeDirectDependencies( private void processUnresolvedNodes( LockingModeResolver.LockingModes lockingModeMap, - IndexBasedDependencyGraphBuilder graphBuilder, + PackageDependencyGraphBuilder graphBuilder, Queue unresolvedNodes) { while (!unresolvedNodes.isEmpty()) { UnresolvedNode unresolvedNode = unresolvedNodes.remove(); DependencyNode pkgNode = unresolvedNode.dependencyNode(); - LockingMode lockingMode = unresolvedNode.lockingMode(); - PackageDescriptor pkg = pkgNode.pkgDesc(); - List indexPackageVersions = index.getPackage(pkg.org().value(), pkg.name().value()); - if (indexPackageVersions == null || indexPackageVersions.isEmpty()) { - throw new ProjectException("Package not found in the index: " + pkg); - } + PackageDescriptor packageToResolve = pkgNode.pkgDesc(); + // TODO: make locking mode, a part of the node itself. - BlendedManifest.Dependency manifestPkg = blendedManifest.dependency(pkg.org(), pkg.name()).orElse(null); - IndexPackage selectedPackage = getLatestCompatibleIndexVersion( - indexPackageVersions, pkg, manifestPkg, graphBuilder, lockingMode); + PackageLockingMode packageLockingMode = unresolvedNode.packageLockingMode(); + PackageVersion nodeVersion = packageToResolve.version(); + BlendedManifest.Dependency manifestPackage = blendedManifest.dependency( + packageToResolve.org(), packageToResolve.name()).orElse(null); + PackageVersion manifestVersion = manifestPackage != null ? manifestPackage.version() : null; + + PackageVersion graphVersion = Optional.ofNullable( + graphBuilder.getDependency(packageToResolve.org(), packageToResolve.name())) + .map(PackageDescriptor::version) + .orElse(null); // Replace `DependencyType` with the actual type of the returned object + boolean isCustomOrLocalRepo = manifestPackage != null && (manifestPackage.isFromLocalRepository() || + manifestPackage.isFromCustomRepository()); + // look at pkg, manifestPkg, packageLockingMode, graphBuilder and decide the reference version to be passed. + Optional version = getCurrentlyBestVersion(nodeVersion, manifestVersion, graphVersion, + packageLockingMode, isCustomOrLocalRepo); + if (version.isPresent()) { + if (packageToResolve.repository().isEmpty()) { // central repo + packageToResolve = PackageDescriptor.from(packageToResolve.org(), packageToResolve.name(), + version.get(), packageToResolve.getDeprecated(), packageToResolve.getDeprecationMsg()); + } else { // custom/ local repo + packageToResolve = PackageDescriptor.from(packageToResolve.org(), packageToResolve.name(), + version.get(), packageToResolve.repository().get()); + } + } + ResolutionRequest resolutionRequest = ResolutionRequest.from( + packageToResolve, pkgNode.scope(), pkgNode.resolutionType(), packageLockingMode); + PackageMetadataResponse response = packageResolver.resolvePackageMetadata( + resolutionRequest, resolutionOptions); + if (response.resolutionStatus() == ResolutionResponse.ResolutionStatus.UNRESOLVED) { + // TODO error + throw new ProjectException("Failed to resolve package: " + packageToResolve); + } DependencyNode updatedPkgNode = new DependencyNode( - PackageDescriptor.from(selectedPackage.org(), selectedPackage.name(), selectedPackage.version(), - selectedPackage.repository()), + response.resolvedDescriptor(), pkgNode.scope(), pkgNode.resolutionType()); graphBuilder.addVertex(updatedPkgNode); - for (IndexDependency dep : selectedPackage.dependencies()) { - + for (PackageDescriptor dep : response.directDependencies()) { // If there is a higher version of the dependency is already in the graph, we use that. PackageVersion depVersion = dep.version(); PackageDescriptor currentIndexDependency = graphBuilder.getDependency(dep.org(), dep.name()); @@ -211,7 +189,7 @@ private void processUnresolvedNodes( // TODO: the scope of the dependency is currently not recorded in the index. Can we safely do that? // The testOnly scoped packages won't be needed by any transitive dependencies. DependencyNode depNode = new DependencyNode(depDesc, pkgNode.scope(), pkgNode.resolutionType()); - LockingMode transitiveDepLockingMode = isNewDependency(depNode)? + PackageLockingMode transitiveDepLockingMode = isNewDependency(depNode) ? lockingModeMap.newTransitiveDepMode() : lockingModeMap.existingTransitiveDepMode(); unresolvedNodes.add(new UnresolvedNode(depNode, transitiveDepLockingMode)); graphBuilder.addDependency(updatedPkgNode, depNode); @@ -219,120 +197,61 @@ private void processUnresolvedNodes( } } - // TODO: refactor and make this method pretty - // Consider the repositories, scope etc here. - // Filter by deprecated status and the platform as well. - private IndexPackage getLatestCompatibleIndexVersion(List indexPackages, - PackageDescriptor indexRecordedPkg, - BlendedManifest.Dependency manifestRecordedPkg, - IndexBasedDependencyGraphBuilder graph, - LockingMode lockingMode) { - // If this context is restricted and invalid, we should throw an error. - if (lockingMode.equals(LockingMode.INVALID)) { - throw new ProjectException("Invalid state"); // TODO: have proper errors with the reason for the invalid state. - } - - // If the package is from the local repository, we should pick the exact version. - if (manifestRecordedPkg != null && manifestRecordedPkg.isFromLocalRepository()) { - // TODO: look at how we should handle the local repos with index. Do we merge the local ones into in memory? - return index.getVersion(manifestRecordedPkg.org().value(), manifestRecordedPkg.name().value(), manifestRecordedPkg.version(), "local") - .orElseThrow(() -> new ProjectException("Package not found in the local index: " + indexRecordedPkg)); - } - - // if the locking mode is LOCKED, we return the version recorded in the manifest. - if (lockingMode.equals(LockingMode.LOCKED)) { - if (manifestRecordedPkg == null) { + private Optional getCurrentlyBestVersion( + PackageVersion queuedVersion, + PackageVersion manifestVersion, + PackageVersion graphVersion, + PackageLockingMode packageLockingMode, + boolean isCustomOrLocalRepo) { + if (packageLockingMode.equals(PackageLockingMode.INVALID)) { + throw new ProjectException("Invalid state"); + } // TODO: have proper errors with the reason for the invalid state. + if (packageLockingMode.equals(PackageLockingMode.LOCKED)) { + if (manifestVersion == null) { throw new ProjectException("Cannot have new dependencies with the LOCKED update policy"); } - return index.getVersion(manifestRecordedPkg.org().value(), manifestRecordedPkg.name().value(), manifestRecordedPkg.version()) - .orElseThrow(() -> new ProjectException("Package not found in the index: " + indexRecordedPkg)); + return Optional.of(manifestVersion); + } + if (isCustomOrLocalRepo) { + return Optional.ofNullable(manifestVersion); } - SemanticVersion currentBallerinaVersion = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()); + Optional candidatePkg = Optional.ofNullable(queuedVersion); - Optional candidatePkg = Optional.empty(); + candidatePkg = compareAndUpdate(candidatePkg, manifestVersion, "manifest"); + candidatePkg = compareAndUpdate(candidatePkg, graphVersion, "graph"); - // compare with the previously fetched value from the index - if (indexRecordedPkg.version() != null) { - Optional indexPkg = index.getVersion(indexRecordedPkg.org().value(), indexRecordedPkg.name().value(), indexRecordedPkg.version()); - if (indexPkg.isEmpty()) { - throw new ProjectException("Package not found in the index: " + indexRecordedPkg); - } - candidatePkg = indexPkg; - } + return candidatePkg; + } - // compare with the version recorded in the blended manifest - if (manifestRecordedPkg != null) { - Optional manifestIndexPkg = index.getVersion(manifestRecordedPkg.org().value(), manifestRecordedPkg.name().value(), manifestRecordedPkg.version()); - if (manifestIndexPkg.isEmpty()) { - throw new ProjectException("Package not found in the index: " + manifestRecordedPkg.org() + "/" + manifestRecordedPkg.name() + ":" + manifestRecordedPkg.version()); - } - if (candidatePkg.isEmpty()) { - candidatePkg = manifestIndexPkg; - } else if (candidatePkg.get().version() - .compareTo(manifestIndexPkg.get().version()) == VersionCompatibilityResult.INCOMPATIBLE) { - throw new ProjectException("Incompatible versions '" - + manifestIndexPkg.get().version() + "', '" + candidatePkg.get().version() - + "' found in the index for package: '" + indexRecordedPkg.org() + "/" + indexRecordedPkg.name() + "'"); - } else if (candidatePkg.get().version() - .compareTo(manifestIndexPkg.get().version()) == VersionCompatibilityResult.LESS_THAN) { - candidatePkg = manifestIndexPkg; - } - } + private Optional compareAndUpdate( + Optional candidatePkg, + PackageVersion newVersion, + String versionType) { - PackageDescriptor graphRecordedPkg = graph.getDependency(indexRecordedPkg.org(), indexRecordedPkg.name()); - if (graphRecordedPkg != null) { - Optional graphIndexPkg = index.getVersion(graphRecordedPkg.org().value(), graphRecordedPkg.name().value(), graphRecordedPkg.version()); - if (graphIndexPkg.isEmpty()) { - throw new ProjectException("Package not found in the index: " + graphRecordedPkg.org() + "/" + graphRecordedPkg.name() + ":" + graphRecordedPkg.version()); - } - if (candidatePkg.isEmpty()) { - candidatePkg = graphIndexPkg; - } else if (candidatePkg.get().version().compareTo(graphRecordedPkg.version()) == VersionCompatibilityResult.INCOMPATIBLE) { - throw new ProjectException("Incompatible versions '" - + graphRecordedPkg.version() + "', '" + candidatePkg.get().version() - + "' found in the index for package: '" + indexRecordedPkg.org() + "/" + indexRecordedPkg.name() + "'"); - } else if (candidatePkg.get().version() - .compareTo(graphRecordedPkg.version()) == VersionCompatibilityResult.LESS_THAN) { - candidatePkg = graphIndexPkg; - } - } - List stableIndexPackages = indexPackages.stream().filter(pkg -> !pkg.version().value().isPreReleaseVersion()).toList(); - for (IndexPackage indexPackage : stableIndexPackages) { // TODO: move the loop to another method - // Distribution version check - if (currentBallerinaVersion.major() != indexPackage.ballerinaVersion().major() - || currentBallerinaVersion.minor() < indexPackage.ballerinaVersion().minor()) { - continue; - } - if (candidatePkg.isEmpty() - || isAllowedVersionBump(candidatePkg.get().version(), indexPackage.version(), lockingMode)) { - candidatePkg = Optional.of(indexPackage); - } + if (newVersion == null) { + return candidatePkg; } - if (candidatePkg.isPresent()) { - return candidatePkg.get(); - } - // If no matching version found, we rely on pre-release versions - List preReleaseIndexPackages = indexPackages.stream().filter(pkg -> pkg.version().value().isPreReleaseVersion()).toList(); - for (IndexPackage indexPackage : preReleaseIndexPackages) { - // Distribution version check - if (currentBallerinaVersion.major() != indexPackage.ballerinaVersion().major() - || currentBallerinaVersion.minor() < indexPackage.ballerinaVersion().minor()) { - continue; - } - if (candidatePkg.isEmpty() - || isAllowedVersionBump(candidatePkg.get().version(), indexPackage.version(), lockingMode)) { - candidatePkg = Optional.of(indexPackage); - } + + if (candidatePkg.isEmpty()) { + return Optional.of(newVersion); } - if (candidatePkg.isPresent()) { - return candidatePkg.get(); + + SemanticVersion.VersionCompatibilityResult comparisonResult = candidatePkg.get().value() + .compareTo(newVersion.value()); + + if (comparisonResult == VersionCompatibilityResult.LESS_THAN) { + return Optional.of(newVersion); + } else if (comparisonResult == VersionCompatibilityResult.INCOMPATIBLE) { + throw new ProjectException("Incompatible versions '" + newVersion + "', '" + + candidatePkg.get() + "' found in the index for package type: '" + versionType + "'"); } - throw new ProjectException("No compatible version found in the index for package: " + indexRecordedPkg); + + return candidatePkg; } private boolean areNewDirectDependenciesAdded(Collection directDependencies) { - for(DependencyNode directDependency: directDependencies) { + for (DependencyNode directDependency: directDependencies) { if (isDirectDependencyNewlyAdded(directDependency)) { return true; } @@ -364,25 +283,6 @@ private boolean isNewDependency(DependencyNode dependency) { return manifestDep.isEmpty(); } - private boolean isAllowedVersionBump( - PackageVersion currentPackageVersion, // this can be null - PackageVersion newPackageVersion, - LockingMode lockingMode) { - SemanticVersion currentVersion = currentPackageVersion.value(); - SemanticVersion newVersion = newPackageVersion.value(); - VersionCompatibilityResult compatibility = currentVersion.compareTo(newVersion); - return switch (lockingMode) { - case LATEST -> compatibility == VersionCompatibilityResult.LESS_THAN - || newVersion.major() > currentVersion.major(); - case SOFT -> compatibility == VersionCompatibilityResult.LESS_THAN; - case MEDIUM -> currentVersion.major() == newVersion.major() - && currentVersion.minor() == newVersion.minor() - && currentVersion.patch() < newVersion.patch(); - case HARD, LOCKED -> false; - case INVALID -> false; - }; - } - private Collection resolvePackages(Collection moduleLoadRequests) { // Get the direct dependencies of the current package. // This list does not contain langlib and the root package. @@ -435,371 +335,6 @@ private Collection resolvePackages(Collection return directDeps; } - private void populateStaticDependencyGraph(Collection directDependencies) { - List errorNodes = directDependencies.stream() - .filter(DependencyNode::errorNode).toList(); - for (DependencyNode errorNode : errorNodes) { - graphBuilder.addErroneousDependency( - rootPkgDesc, errorNode.pkgDesc, errorNode.scope, errorNode.resolutionType); - } - directDependencies.removeAll(errorNodes); - - Collection pkgMetadataResponses = resolveDirectDependencies(directDependencies); - this.unresolvedDeps = new HashSet<>(); - Set resolvedDeps = new HashSet<>(); - for (PackageMetadataResponse resolutionResp : pkgMetadataResponses) { - if (resolutionResp.resolutionStatus() == ResolutionResponse.ResolutionStatus.UNRESOLVED) { - // TODO Report diagnostics - if (resolutionOptions.dumpRawGraphs() || resolutionOptions.dumpGraph()) { - ResolutionRequest resolutionRequest = resolutionResp.packageLoadRequest(); - DependencyNode dependencyNode = new DependencyNode( - resolutionRequest.packageDescriptor(), - resolutionRequest.scope(), - resolutionRequest.resolutionType()); - unresolvedDeps.add(dependencyNode); - graphBuilder.addUnresolvedDirectDepToRawGraph(dependencyNode); - } - continue; - } - ResolutionRequest resolutionReq = resolutionResp.packageLoadRequest(); - PackageDescriptor resolvedPkgDesc = resolutionResp.resolvedDescriptor(); - DependencyResolutionType resolutionType = resolutionReq.resolutionType(); - PackageDependencyScope scope = resolutionReq.scope(); - - // Merge the dependency graph only if the node is accepted by the graphBuilder - NodeStatus nodeStatus = graphBuilder.addResolvedDependency(rootPkgDesc, - resolvedPkgDesc, scope, resolutionType); - if (nodeStatus == NodeStatus.ACCEPTED) { - mergeGraph(resolvedPkgDesc, - resolutionResp.dependencyGraph().orElseThrow( - () -> new IllegalStateException("Graph cannot be null in the resolved dependency: " + - resolvedPkgDesc.toString())), - scope, resolutionType); - } - resolvedDeps.add(new DependencyNode(resolvedPkgDesc, scope, resolutionType)); - } - if (resolutionOptions.dumpRawGraphs() || resolutionOptions.dumpGraph()) { - HashSet unresolvedNodes = new HashSet<>(graphBuilder.getAllDependencies()); - unresolvedNodes.removeAll(resolvedDeps); - unresolvedDeps.addAll(unresolvedNodes); - } - dumpInitialGraph(); - } - - private Collection resolveDirectDependencies(Collection directDeps) { - // Set the default locking mode based on the sticky build option. - PackageLockingMode defaultLockingMode = resolutionOptions.sticky() ? - PackageLockingMode.HARD : resolutionOptions.packageLockingMode(); - List resolutionRequests = new ArrayList<>(); - - for (DependencyNode directDependency : directDeps) { - PackageLockingMode lockingMode = defaultLockingMode; - PackageDescriptor pkgDesc = directDependency.pkgDesc(); - Optional dependency = blendedManifest.lockedDependency( - pkgDesc.org(), pkgDesc.name()); - if (dependency.isPresent()) { - if (dependency.get().relation() == BlendedManifest.DependencyRelation.TRANSITIVE) { - // If the dependency is a direct dependency then use the version otherwise leave it. - // The situation is that an indirect dependency(previous compilation) has become a - // direct dependency (this compilation). Here we ignore the previous indirect dependency version - // and look up Ballerina central repository for the latest version which is in the same - // compatible range. - lockingMode = PackageLockingMode.SOFT; - } - } else { - // If the user has specified the dependency from the local repo/custom repo, - // we must resolve the exact version provided for the dependency - dependency = blendedManifest.userSpecifiedDependency(pkgDesc.org(), pkgDesc.name()); - if (dependency.isPresent() && (dependency.get().isFromLocalRepository() || - dependency.get().isFromCustomRepository())) { - lockingMode = PackageLockingMode.HARD; - } - } - resolutionRequests.add(ResolutionRequest.from(pkgDesc, directDependency.scope(), - directDependency.resolutionType(), lockingMode)); - } - - return packageResolver.resolvePackageMetadata(resolutionRequests, resolutionOptions); - } - - private void mergeGraph(PackageDescriptor rootNode, - DependencyGraph dependencyGraph, - PackageDependencyScope scope, - DependencyResolutionType resolutionType) { - Collection directDependencies = dependencyGraph.getDirectDependencies(rootNode); - for (PackageDescriptor directDep : directDependencies) { - NodeStatus nodeStatus; - DependencyGraph dependencyGraphFinal; - if (directDep.isBuiltInPackage()) { - // a built-in dependency will have the same version (0.0.0) across Ballerina distributions - // but their dependencies may change. Therefore, - // we need to always get the dependency graph of built-in packages from the current distribution - dependencyGraphFinal = getBuiltInPkgDescDepGraph(scope, directDep); - // Builtin package versions are always resolved - nodeStatus = graphBuilder.addResolvedDependency(rootNode, directDep, scope, resolutionType); - } else { - dependencyGraphFinal = dependencyGraph; - nodeStatus = graphBuilder.addUnresolvedDependency(rootNode, directDep, scope, resolutionType); - } - - // Merge the dependency graph only if the node is accepted by the graphBuilder - if (nodeStatus == NodeStatus.ACCEPTED) { - mergeGraph(directDep, dependencyGraphFinal, scope, resolutionType); - } - } - } - - private DependencyGraph getBuiltInPkgDescDepGraph( - PackageDependencyScope scope, PackageDescriptor directDep) { - Collection packageMetadataResponses = packageResolver.resolvePackageMetadata( - Collections.singletonList(ResolutionRequest.from(directDep, scope)), resolutionOptions); - if (packageMetadataResponses.isEmpty()) { - // This condition cannot be met since a built-in package is always expected to be available in the dist - throw new IllegalStateException("built-in package not found in distribution: " + directDep.toString()); - } - PackageMetadataResponse packageMetadataResponse = packageMetadataResponses.iterator().next(); - Optional> packageDescriptorDependencyGraph = - packageMetadataResponse.dependencyGraph(); - - return packageDescriptorDependencyGraph - .orElseThrow(() -> new IllegalStateException( - "Graph cannot be null in the built-in package: " + directDep.toString())); - } - - private void updateDependencyVersions() { - // Remove all dangling nodes in the graph builder. - graphBuilder.removeDanglingNodes(); - // Get unresolved nodes. This list is based on the sticky option. - Collection unresolvedNodes = getUnresolvedNode(); - List errorNodes = new ArrayList<>(); - - // Create ResolutionRequests for all unresolved nodes by looking at the blended nodes - List unresolvedRequests = new ArrayList<>(); - for (DependencyNode unresolvedNode : unresolvedNodes) { - if (unresolvedNode.isError) { - errorNodes.add(unresolvedNode); - continue; - } - PackageDescriptor unresolvedPkgDes = unresolvedNode.pkgDesc(); - Optional blendedDepOptional = - blendedManifest.dependency(unresolvedPkgDes.org(), unresolvedPkgDes.name()); - ResolutionRequest resolutionRequest = getRequestForUnresolvedNode(unresolvedNode, - blendedDepOptional.orElse(null)); - if (resolutionRequest == null) { - // There is a version incompatibility. - // We mark it as an error node and skip to the next node. - errorNodes.add(new DependencyNode( - unresolvedNode.pkgDesc, - unresolvedNode.scope, - unresolvedNode.resolutionType, - true)); - continue; - } - unresolvedRequests.add(resolutionRequest); - } - - // Resolve unresolved nodes to see whether there exist newer versions - Collection pkgMetadataResponses = - packageResolver.resolvePackageMetadata(unresolvedRequests, resolutionOptions); - - // Update the graph with new versions of dependencies (if any) - addUpdatedPackagesToGraph(pkgMetadataResponses); - addErrorNodesToGraph(errorNodes); - - dumpIntermediateGraph(1); - } - - private void addErrorNodesToGraph(List errorNodes) { - for (DependencyNode errorNode : errorNodes) { - graphBuilder.addErrorNode(errorNode.pkgDesc, errorNode.scope, errorNode.resolutionType); - if (resolutionOptions.dumpGraph() || resolutionOptions.dumpRawGraphs()) { - unresolvedDeps.remove(errorNode); - } - } - } - - /** - * Returns a ResolutionRequest instance for the given unresolved node by considering the details - * recorded in BlendedManifest. - * - * @param unresolvedNode the unresolved node - * @param blendedDep the dependency recorded in either Dependencies.toml or Ballerina.toml - * @return ResolutionRequest resolution request for the unresolved node - */ - private ResolutionRequest getRequestForUnresolvedNode(DependencyNode unresolvedNode, - BlendedManifest.Dependency blendedDep) { - if (blendedDep == null) { - return ResolutionRequest.from(unresolvedNode.pkgDesc(), unresolvedNode.scope(), - unresolvedNode.resolutionType(), resolutionOptions.packageLockingMode()); - } - - if (blendedDep.isError()) { - // The conflict is already identified when creating the BlendedManifest. - // So we just return a null. - return null; - } - - // Compare blendedDep version with the unresolved version - VersionCompatibilityResult versionCompResult = blendedDep.version().compareTo( - unresolvedNode.pkgDesc().version()); - if (versionCompResult == VersionCompatibilityResult.GREATER_THAN || - versionCompResult == VersionCompatibilityResult.EQUAL) { - PackageLockingMode lockingMode = resolutionOptions.sticky() || blendedDep.isFromLocalRepository() ? - PackageLockingMode.HARD : resolutionOptions.packageLockingMode(); - PackageDescriptor blendedDepPkgDesc = PackageDescriptor.from(blendedDep.org(), blendedDep.name(), - blendedDep.version(), blendedDep.repository()); - return ResolutionRequest.from(blendedDepPkgDesc, unresolvedNode.scope(), - unresolvedNode.resolutionType(), lockingMode); - } else if (versionCompResult == VersionCompatibilityResult.LESS_THAN) { - return ResolutionRequest.from(unresolvedNode.pkgDesc(), unresolvedNode.scope(), - unresolvedNode.resolutionType(), resolutionOptions.packageLockingMode()); - } else { - // Blended Dep version is incompatible with the unresolved node. - // We report a diagnostic and return null. - String depInfo = blendedDep.org() + "/" + blendedDep.name(); - String sourceFile = blendedDep.origin() == BlendedManifest.DependencyOrigin.USER_SPECIFIED ? - ProjectConstants.BALLERINA_TOML : ProjectConstants.DEPENDENCIES_TOML; - - DiagnosticInfo diagnosticInfo = new DiagnosticInfo( - ProjectDiagnosticErrorCode.INCOMPATIBLE_DEPENDENCY_VERSIONS.diagnosticId(), - "Incompatible versions: " + depInfo + ". " + - "Version specified in " + sourceFile + ": " + blendedDep.version() + - " and the version resolved from other dependencies: " + unresolvedNode.pkgDesc.version(), - DiagnosticSeverity.ERROR); - PackageDiagnostic diagnostic = new PackageDiagnostic( - diagnosticInfo, this.rootPkgDesc.name().toString()); - diagnostics.add(diagnostic); - return null; - } - } - - private Collection getUnresolvedNode() { - if (resolutionOptions.sticky()) { - return graphBuilder.getUnresolvedNodes(); - } else { - // Since sticky = false, we have to update all dependency nodes. - return graphBuilder.getAllDependencies(); - } - } - - private void completeDependencyGraph() { - // This is the second attempt to update versions. - int noOfUpdateAttempts = 2; - - graphBuilder.removeDanglingNodes(); - Collection unresolvedNodes; - Collection pkgMetadataResponses = new ArrayList<>(); - - while (!(unresolvedNodes = graphBuilder.getUnresolvedNodes()).isEmpty()) { - // Create ResolutionRequests for all unresolved nodes by looking at the blended nodes - List unresolvedRequests = new ArrayList<>(unresolvedNodes.size()); - for (DependencyNode unresolvedNode : unresolvedNodes) { - PackageDescriptor unresolvedPkgDes = unresolvedNode.pkgDesc(); - Optional blendedDepOptional = - blendedManifest.userSpecifiedDependency(unresolvedPkgDes.org(), unresolvedPkgDes.name()); - ResolutionRequest resolutionRequest = getRequestForUnresolvedNode(unresolvedNode, - blendedDepOptional.orElse(null)); - if (unresolvedNode.errorNode()) { - pkgMetadataResponses.add(PackageMetadataResponse.createUnresolvedResponse(resolutionRequest)); - continue; - } - unresolvedRequests.add(resolutionRequest); - } - - pkgMetadataResponses.addAll(packageResolver.resolvePackageMetadata(unresolvedRequests, resolutionOptions)); - addUpdatedPackagesToGraph(pkgMetadataResponses); - graphBuilder.removeDanglingNodes(); - - dumpIntermediateGraph(noOfUpdateAttempts++); - } - } - - private void addUpdatedPackagesToGraph(Collection pkgMetadataResponses) { - for (PackageMetadataResponse resolutionResp : pkgMetadataResponses) { - if (resolutionResp.resolutionStatus() == ResolutionResponse.ResolutionStatus.UNRESOLVED) { - // TODO Report diagnostics - continue; - } - - addNodeToGraph(resolutionResp); - } - } - - private void addNodeToGraph(PackageMetadataResponse resolutionResp) { - ResolutionRequest resolutionReq = resolutionResp.packageLoadRequest(); - PackageDescriptor pkgDesc = resolutionResp.resolvedDescriptor(); - PackageDependencyScope scope = resolutionReq.scope(); - DependencyResolutionType resolvedType = resolutionReq.resolutionType(); - - // Merge the dependency graph only if the node is accepted by the graphBuilder - NodeStatus nodeStatus = graphBuilder.addResolvedNode(pkgDesc, scope, resolvedType); - if (nodeStatus == NodeStatus.ACCEPTED) { - mergeGraph(pkgDesc, resolutionResp.dependencyGraph().orElseThrow( - () -> new IllegalStateException("Graph cannot be null in the resolved dependency: " + - pkgDesc.toString())), - scope, resolvedType); - } - - // Remove from the unresolved nodes list for dumping the raw graph - if (resolutionOptions.dumpGraph() || resolutionOptions.dumpRawGraphs()) { - unresolvedDeps.remove(new DependencyNode(pkgDesc, scope, resolvedType)); - } - } - - private DependencyGraph buildFinalDependencyGraph() { - DependencyGraph dependencyGraph = graphBuilder.buildGraph(); - this.diagnostics.addAll(graphBuilder.diagnostics()); - dumpFinalGraph(dependencyGraph); - return dependencyGraph; - } - - private void dumpInitialGraph() { - if (!resolutionOptions.dumpRawGraphs()) { - return; - } - String serializedGraph = serializeRawGraph("Initial"); - dependencyGraphDump += "\n"; - dependencyGraphDump += (serializedGraph + "\n"); - } - - private void dumpIntermediateGraph(int noOfUpdateAttempts) { - if (!resolutionOptions.dumpRawGraphs()) { - return; - } - - String serializedGraph = serializeRawGraph("Version update attempt " + noOfUpdateAttempts); - dependencyGraphDump += "\n"; - dependencyGraphDump += (serializedGraph + "\n"); - } - - private void dumpFinalGraph(DependencyGraph dependencyGraph) { - if (!resolutionOptions.dumpGraph() && !resolutionOptions.dumpRawGraphs()) { - return; - } - - List unresolvedDirectDeps = new ArrayList<>( - graphBuilder.rawGraph().getDirectDependencies(dependencyGraph.getRoot())); - Collection resolvedDirectDeps = - dependencyGraph.getDirectDependencies(dependencyGraph.getRoot()); - unresolvedDirectDeps.removeAll(resolvedDirectDeps); - - String serializedGraph; - if (resolutionOptions.dumpRawGraphs()) { - serializedGraph = DotGraphs.serializeDependencyNodeGraph( - dependencyGraph, "Final", this.unresolvedDeps, unresolvedDirectDeps); - } else { - serializedGraph = DotGraphs.serializeDependencyNodeGraph( - dependencyGraph, this.unresolvedDeps, unresolvedDirectDeps); - } - dependencyGraphDump += "\n"; - dependencyGraphDump += (serializedGraph + "\n"); - } - - private String serializeRawGraph(String graphName) { - DependencyGraph initialGraph = graphBuilder.rawGraph(); - return DotGraphs.serializeDependencyNodeGraph(initialGraph, graphName, this.unresolvedDeps); - } - public String dumpGraphs() { return dependencyGraphDump; } @@ -892,9 +427,14 @@ public int compareTo(DependencyNode other) { } } - enum VersionOrigin { - DIRECT, - MANIFEST, - INDEX + /** + * Represents an unresolved node that goes in the queue in the context of dependency resolution. + * + * @param dependencyNode The dependency node that needs to be resolved + * @param packageLockingMode The locking mode of the package + */ + private record UnresolvedNode( + ResolutionEngine.DependencyNode dependencyNode, + PackageLockingMode packageLockingMode) { } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/UnresolvedNode.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/UnresolvedNode.java deleted file mode 100644 index a3e092cda4fa..000000000000 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/UnresolvedNode.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; - -import io.ballerina.projects.environment.LockingMode; - -record UnresolvedNode(ResolutionEngine.DependencyNode dependencyNode, LockingMode lockingMode) { -} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/environment/DefaultPackageResolver.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/environment/DefaultPackageResolver.java index 42256bc7fc74..c93183941c48 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/environment/DefaultPackageResolver.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/environment/DefaultPackageResolver.java @@ -39,8 +39,8 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -123,62 +123,32 @@ public Collection resolvePackageNames(Collection resolvePackageMetadata(Collection requests, - ResolutionOptions options) { - Collection localRepoRequests = new ArrayList<>(); - Map> customRepoRequestMap = new HashMap<>(); - for (ResolutionRequest request : requests) { - Optional repository = request.packageDescriptor().repository(); - if (repository.isPresent() && repository.get().equals(ProjectConstants.LOCAL_REPOSITORY_NAME)) { - localRepoRequests.add(request); - } else if (repository.isPresent() && customRepos.containsKey(repository.get())) { - PackageRepository customRepository = customRepos.get(repository.get()); - - if (customRepoRequestMap.containsKey(customRepository)) { - customRepoRequestMap.get(customRepository).add(request); - } else { - ArrayList requestList = new ArrayList<>(); - requestList.add(request); - customRepoRequestMap.put(customRepository, requestList); - } - } - } - - Collection localRepoPackages = localRepoRequests.isEmpty() ? - Collections.emptyList() : - localRepo.getPackageMetadata(localRepoRequests, options); - - Collection allCustomRepoPackages = new ArrayList<>(); - for (Map.Entry> customRepoRequestEntry : - customRepoRequestMap.entrySet()) { - PackageRepository customRepository = customRepoRequestEntry.getKey(); - ArrayList customRepoRequests = customRepoRequestEntry.getValue(); - Collection customRepoPackages = customRepoRequests.isEmpty() ? - Collections.emptyList() : customRepository.getPackageMetadata(customRepoRequests, options); - allCustomRepoPackages.addAll(customRepoPackages); + public PackageMetadataResponse resolvePackageMetadata(ResolutionRequest request, ResolutionOptions options) { + PackageMetadataResponse localRepoPackage = null; + PackageMetadataResponse customRepoPackage = null; + PackageMetadataResponse centralRepoPackage = null; + Optional repository = request.packageDescriptor().repository(); + if (repository.isPresent() && repository.get().equals(ProjectConstants.LOCAL_REPOSITORY_NAME)) { + localRepoPackage = localRepo.getPackageMetadata(request, options); + } else if (repository.isPresent() && customRepos.containsKey(repository.get())) { + PackageRepository customRepository = customRepos.get(repository.get()); + customRepoPackage = customRepository.getPackageMetadata(request, options); } // TODO Send ballerina* org names to dist repo - Collection latestVersionsInDist = - distributionRepo.getPackageMetadata(requests, options); + PackageMetadataResponse distRepoPackage = distributionRepo.getPackageMetadata(request, options); - // Send non built in packages to central - Collection centralLoadRequests = requests.stream() - .filter(r -> !r.packageDescriptor().isBuiltInPackage()) - .toList(); - Collection latestVersionsInCentral = - centralRepo.getPackageMetadata(centralLoadRequests, options); + // Send non builtin package to central + if (!request.packageDescriptor().isBuiltInPackage()) { + centralRepoPackage = centralRepo.getPackageMetadata(request, options); + } // TODO Unit test following merge - List responseDescriptors = new ArrayList<>( - // Since packages can be resolved from multiple repos - // the repos should be provided to the stream in the order of priority. - Stream.of(localRepoPackages, allCustomRepoPackages, latestVersionsInDist, latestVersionsInCentral) - .flatMap(Collection::stream).collect(Collectors.toMap( - PackageMetadataResponse::packageLoadRequest, Function.identity(), - (PackageMetadataResponse x, PackageMetadataResponse y) -> { - // There will be 2 iterations (number of repos-1) and the returned - // value of the first iteration will be the 'x' for the next iteration. + // Since packages can be resolved from multiple repos + // the repos should be provided to the stream in the order of priority. + return Stream.of(localRepoPackage, customRepoPackage, distRepoPackage, centralRepoPackage) + .filter(Objects::nonNull) + .reduce((x, y) -> { if (y.resolutionStatus().equals(ResolutionStatus.UNRESOLVED)) { return x; } @@ -193,9 +163,7 @@ public Collection resolvePackageMetadata(Collection>> packageMap; - public static final Index EMPTY_INDEX = new Index(); - - public Index() { - this.packageMap = new HashMap<>(); - } - - public Optional getVersion(String orgName, String packageName, PackageVersion version) { - return getVersion(orgName, packageName, version, null); - } - - public Optional getVersion(String orgName, String packageName, PackageVersion version, - String repository) { - if (this.packageMap.get(orgName) == null) { - return Optional.empty(); - } - List packageMap = this.packageMap.get(orgName).get(packageName); - if (packageMap == null) { - return Optional.empty(); - } - Stream versions = packageMap.stream().filter(pkg -> pkg.version().equals(version)); - if (repository != null) { - return versions.filter(pkg -> repository.equals(pkg.repository())).findFirst(); - } - return versions.findFirst(); - } - - public void putVersion(IndexPackage pkg) { - List packageMap = this.packageMap.computeIfAbsent( - pkg.org().toString(), k -> new HashMap<>()).computeIfAbsent(pkg.name().toString(), k -> new ArrayList<>()); - packageMap.add(pkg); - } - - public List getPackage(String orgName, String packageName) { - if (this.packageMap.get(orgName) == null) { - return new ArrayList<>(); - } - return packageMap.get(orgName).get(packageName); - } - - public List getPackage(String orgName, - String packageName, - String supportedPlatform, - SemanticVersion ballerinaVersion) { - if (this.packageMap.get(orgName) == null) { - return new ArrayList<>(); - } - return packageMap.get(orgName).get(packageName).stream().filter(pkg -> - pkg.supportedPlatform().equals(supportedPlatform) && pkg.ballerinaVersion().equals(ballerinaVersion) - ).toList(); - } - - public List getPackageMatchingModule(String orgName, String moduleName, String ballerinaVersionStr) { - SemanticVersion ballerinaVersion = SemanticVersion.from(ballerinaVersionStr); - if (this.packageMap.get(orgName) == null) { - return new ArrayList<>(); - } - return packageMap.get(orgName).values().stream().flatMap(List::stream) - .filter(pkg -> pkg.modules().stream().anyMatch(module -> module.name().equals(moduleName)) && - ballerinaVersion.greaterThanOrEqualTo(pkg.ballerinaVersion())) - .toList(); - } - - public void putPackages(List pkgs) { - for (IndexPackage descriptor : pkgs) { - putVersion(descriptor); - } - } -} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java index 5aa4de4380cc..b75277587fde 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java @@ -22,74 +22,83 @@ import io.ballerina.projects.PackageName; import io.ballerina.projects.PackageOrg; import io.ballerina.projects.PackageVersion; -import io.ballerina.projects.SemanticVersion; import java.util.List; +/** + * Represents a package in the index. + * + * @since 2201.12.0 + */ public class IndexPackage { - private final PackageOrg packageOrg; - private final PackageName packageName; - private final PackageVersion packageVersion; - private final String repository; + private final PackageDescriptor packageDescriptor; private final String supportedPlatform; - private final SemanticVersion ballerinaVersion; - private final List dependencies; + private final String ballerinaVersion; + private final List dependencies; private final List modules; + private final boolean isDeprecated; + private final String deprecationMsg; - // TODO: add other fields as necessary private IndexPackage( - PackageOrg packageOrg, - PackageName packageName, - PackageVersion packageVersion, - String repository, + PackageDescriptor packageDescriptor, String supportedPlatform, - SemanticVersion ballerinaVersion, - List dependencies, - List modules) { - this.packageOrg = packageOrg; - this.packageName = packageName; - this.packageVersion = packageVersion; - this.repository = repository; + String ballerinaVersion, + List dependencies, + List modules, + boolean isDeprecated, + String deprecationMsg) { + this.packageDescriptor = packageDescriptor; this.supportedPlatform = supportedPlatform; this.ballerinaVersion = ballerinaVersion; this.dependencies = dependencies; this.modules = modules; + this.isDeprecated = isDeprecated; + this.deprecationMsg = deprecationMsg; + } + + static IndexPackage from( + PackageDescriptor packageDescriptor, + String supportedPlatform, + String ballerinaVersion, + List dependencies, + List modules, + boolean isDeprecated, + String deprecationMsg) { + return new IndexPackage(packageDescriptor, supportedPlatform, ballerinaVersion, dependencies, modules, + isDeprecated, deprecationMsg); } - // TODO: If we pass all data as-it-is, we don't need this from method - public static IndexPackage from( + static IndexPackage from( PackageOrg packageOrg, PackageName packageName, PackageVersion packageVersion, - String repository, String supportedPlatform, - SemanticVersion ballerinaVersion, - List dependencies, - List modules) { - return new IndexPackage(packageOrg, packageName, packageVersion, repository, supportedPlatform, ballerinaVersion, dependencies, modules); + String ballerinaVersion, + List dependencies, + List modules, + boolean isDeprecated, + String deprecationMsg) { + return from(PackageDescriptor.from(packageOrg, packageName, packageVersion), + supportedPlatform, ballerinaVersion, dependencies, modules, isDeprecated, deprecationMsg); } public PackageName name() { - return packageName; + return packageDescriptor.name(); } public PackageOrg org() { - return packageOrg; + return packageDescriptor.org(); } public PackageVersion version() { - return packageVersion; - } - - public String repository() { - return repository; + return packageDescriptor.version(); } - public List dependencies() { + public List dependencies() { return dependencies; } - public SemanticVersion ballerinaVersion() { + public String ballerinaVersion() { return ballerinaVersion; } @@ -112,4 +121,12 @@ public String name() { return name; } } + + public boolean isDeprecated() { + return isDeprecated; + } + + public String deprecationMsg() { + return deprecationMsg; + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackageAdapter.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackageAdapter.java new file mode 100644 index 000000000000..20ae34fc30bf --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackageAdapter.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal.index; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.ballerina.projects.PackageDescriptor; +import io.ballerina.projects.PackageName; +import io.ballerina.projects.PackageOrg; +import io.ballerina.projects.PackageVersion; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * Deserializer for {@link IndexPackage}. + * + * @since 2201.12.0 + */ +public class IndexPackageAdapter implements JsonDeserializer { + + /** + * Deserialize the Json data to an {@link IndexPackage} object. + * + * @param json The Json data being deserialized + * @param typeOfT The type of the Object to deserialize to + * @param context The context which deserialization is taking place + * @return The deserialized {@link IndexPackage} object + */ + @Override + public IndexPackage deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + JsonObject jsonObject = json.getAsJsonObject(); + + PackageDescriptor packageDescriptor = deserializePackageDescriptor(json); + + String supportedPlatform = jsonObject.get("platform").getAsString(); + String ballerinaVersion = jsonObject.get("ballerina_version").getAsString(); + boolean isDeprecated = jsonObject.get("is_deprecated").getAsBoolean(); + String deprecationMsg = jsonObject.get("deprecation_message").getAsString(); + + List dependencies = deserializeDependencies(jsonObject.get("dependencies")); + List modules = deserializeModules(jsonObject.get("modules")); + + return IndexPackage.from( + packageDescriptor, + supportedPlatform, + ballerinaVersion, + dependencies, + modules, + isDeprecated, + deprecationMsg); + } + + private List deserializeDependencies(JsonElement json) { + List list = new ArrayList<>(); + JsonArray jsonArray = json.getAsJsonArray(); + for (JsonElement element : jsonArray) { + list.add(deserializePackageDescriptor(element)); + } + return list; + } + + private PackageDescriptor deserializePackageDescriptor(JsonElement json) { + JsonObject jsonObject = json.getAsJsonObject(); + PackageOrg packageOrg = PackageOrg.from(jsonObject.get("org").getAsString()); + PackageName packageName = PackageName.from(jsonObject.get("name").getAsString()); + PackageVersion packageVersion = PackageVersion.from(jsonObject.get("version").getAsString()); + return PackageDescriptor.from(packageOrg, packageName, packageVersion); + } + + private List deserializeModules(JsonElement modules) { + List list = new ArrayList<>(); + JsonArray jsonArray = modules.getAsJsonArray(); + for (JsonElement element : jsonArray) { + JsonObject jsonObject = element.getAsJsonObject(); + String moduleName = jsonObject.get("name").getAsString(); + list.add(new IndexPackage.Module(moduleName)); + } + return list; + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndex.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndex.java new file mode 100644 index 000000000000..ba295792f680 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndex.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal.index; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import io.ballerina.projects.PackageName; +import io.ballerina.projects.PackageOrg; +import io.ballerina.projects.PackageVersion; +import io.ballerina.projects.util.ProjectUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Represents the package index as an in-memory table of organization names and package names. + * + * @since 2201.12.0 + */ +public class PackageIndex { + private final Table> packageTable = HashBasedTable.create(); + private final PackageIndexUpdater updater; + + PackageIndex(PackageIndexUpdater updater) { + this.updater = updater; + } + + void addPackage(List packages) { + for (IndexPackage pkg : packages) { + if (!this.packageTable.contains(pkg.org(), pkg.name())) { + this.packageTable.put(pkg.org(), pkg.name(), new ArrayList<>()); + } + Objects.requireNonNull(this.packageTable.get(pkg.org(), pkg.name())).add(pkg); + } + } + + /** + * Set the offline flag. + * + * @param offline offline flag + */ + public void setOffline(boolean offline) { + this.updater.setOffline(offline); + } + + private void loadPackage(PackageOrg packageOrg, PackageName packageName) { + if (!this.packageTable.contains(packageOrg, packageName)) { + updater.loadPackage(packageOrg, packageName); + } + } + + private void loadOrg(PackageOrg packageOrg) { + updater.loadOrg(packageOrg); + } + + /** + * Get the specified version of a package. + * + * @param packageOrg organization name + * @param packageName package name + * @param version package version + * @return the matching package recorded in the index + */ + public IndexPackage getVersion(PackageOrg packageOrg, PackageName packageName, PackageVersion version) { + loadPackage(packageOrg, packageName); + List packageMap = this.packageTable.get(packageOrg, packageName); + if (packageMap == null) { + return null; + } + return packageMap.stream().filter(pkg -> pkg.version().equals(version)).findAny().orElse(null); + } + + /** + * Get the all available versions of a package. + * + * @param orgName organization name + * @param packageName package name + * @return package versions of the matching package recorded in the index + */ + public List getPackage(PackageOrg orgName, PackageName packageName) { + loadPackage(orgName, packageName); + return this.packageTable.get(orgName, packageName); + } + + /** + * Get the all modules matching a module name in an organization + * @param orgName organization name + * @param moduleName module name + * @return modules of the matching package recorded in the index + */ + public List getPackageContainingModule(PackageOrg orgName, String moduleName) { + loadOrg(orgName); + return packageTable.row(orgName).values().stream().flatMap(List::stream) + .filter(pkg -> pkg.modules().stream().anyMatch(module -> module.name().equals(moduleName)) && + ProjectUtils.isDistributionSupported(pkg.ballerinaVersion())) + .toList(); + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexBuilder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexBuilder.java new file mode 100644 index 000000000000..cb9c9898e6fa --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexBuilder.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal.index; + +import org.eclipse.jgit.transport.CredentialsProvider; +import org.wso2.ballerinalang.util.RepoUtils; + +import java.nio.file.Path; + +import static io.ballerina.projects.util.ProjectConstants.INDEX_DIR_NAME; + +/** + * Builder for creating a {@code PackageIndex}. + * + * @since 2201.12.0 + */ +public class PackageIndexBuilder { + // TODO: add correct values for the below static variables + // TODO: "central.ballerina.io-index" similar to rust's "crates.io-index" + private static final String DEFAULT_INDEX_GIT_REPO_NAME = "ballerina-index-test"; + private static final String DEFAULT_REMOTE_REPO_URL + = "https://github.com/gayaldassanayake/ballerina-index-test.git"; + + private Path indexPath; + private String indexGitRepoName; + private String remoteRepoUrl; + private CredentialsProvider credentialsProvider; + + /** + * Creates a new {@code PackageIndexBuilder} instance. + * The fields are set to default values pointing to the index of the Ballerina central. + * + */ + public PackageIndexBuilder() { + this.indexPath = RepoUtils.createAndGetHomeReposPath().resolve(INDEX_DIR_NAME); + this.indexGitRepoName = DEFAULT_INDEX_GIT_REPO_NAME; + this.remoteRepoUrl = DEFAULT_REMOTE_REPO_URL; + this.credentialsProvider = CredentialsProvider.getDefault(); + } + + /** + * Sets the path to the index. + * + * @param indexPath the path to the index + * @return the updated {@code PackageIndexBuilder} instance + */ + public PackageIndexBuilder indexPath(Path indexPath) { + this.indexPath = indexPath; + return this; + } + + /** + * Sets the name of the git repository where the index is stored. + * + * @param indexGitRepoName the name of the git repository + * @return the updated {@code PackageIndexBuilder} instance + */ + public PackageIndexBuilder indexGitRepoName(String indexGitRepoName) { + this.indexGitRepoName = indexGitRepoName; + return this; + } + + /** + * Sets the URL of the remote repository where the index is stored. + * + * @param remoteRepoUrl the URL of the remote repository + * @return the updated {@code PackageIndexBuilder} instance + */ + public PackageIndexBuilder remoteRepoUrl(String remoteRepoUrl) { + this.remoteRepoUrl = remoteRepoUrl; + return this; + } + + /** + * Sets the credentials provider for the remote repository. + * + * @param credentialsProvider the credentials provider + * @return the updated {@code PackageIndexBuilder} instance + */ + public PackageIndexBuilder credentialsProvider(CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Builds a new {@code PackageIndex} instance. + * + * @return the new {@code PackageIndex} instance + */ + public PackageIndex build() { + PackageIndexUpdater indexUpdater = new PackageIndexUpdater( + indexPath, indexGitRepoName, remoteRepoUrl, credentialsProvider); + PackageIndex packageIndex = new PackageIndex(indexUpdater); + indexUpdater.setPackageIndex(packageIndex); + return packageIndex; + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexUpdater.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexUpdater.java new file mode 100644 index 000000000000..4efbd56a2df7 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexUpdater.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal.index; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.ballerina.projects.PackageName; +import io.ballerina.projects.PackageOrg; +import io.ballerina.projects.ProjectException; +import org.eclipse.jgit.api.CreateBranchCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.wso2.ballerinalang.util.RepoUtils; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.wso2.ballerinalang.util.RepoUtils.SET_BALLERINA_DEV_CENTRAL; + +/** + * This class is responsible for updating the package index repository cache and loading the packages to the index. + * + * @since 2201.12.0 + */ +class PackageIndexUpdater { + private final Path indexDirectory; + private final String indexGitRepoName; + private final String remoteRepoUrl; + private final CredentialsProvider credentialsProvider; + private boolean isIndexRepoUpdated = false; + private PackageIndex packageIndex; + private boolean offline = false; + private final PrintStream outStream = System.out; + + PackageIndexUpdater(Path indexPath, String indexGitRepoName, + String remoteRepoUrl, CredentialsProvider credentialsProvider) { + this.indexDirectory = indexPath; + this.indexGitRepoName = indexGitRepoName; + this.remoteRepoUrl = remoteRepoUrl; + this.credentialsProvider = credentialsProvider; + } + + private void updateIndexRepoCache() { + if (isIndexRepoUpdated) { + return; + } + if (!isIndexFetched()) { + if (offline) { + outStream.println("Index repository is not available. " + + "Try again without the 'offline' flag set to false"); + return; + } + fetchIndex(); + } + checkoutToBranch(); + if (!offline) { + fetchIndexHead(); + } + isIndexRepoUpdated = true; + } + + void setOffline(boolean offline) { + this.offline = offline; + } + + void setPackageIndex(PackageIndex packageIndex) { + this.packageIndex = packageIndex; + } + + public void loadOrg(PackageOrg packageOrg) { + Path orgDir = indexDirectory.resolve(indexGitRepoName).resolve(packageOrg.value()); + if (!Files.exists(orgDir)) { + return; + } + try (Stream files = Files.list(orgDir)) { + for (Path packageFile : files.toList()) { + loadPackage(packageOrg, PackageName.from(packageFile.getFileName().toString().replace(".json", ""))); + } + } catch (IOException e) { + throw new ProjectException("Error reading index files: " + e.getMessage(), e); + } + } + + // TODO: Possible performance improvements. Can research on this area more if needed. + // 1. load all ballerina* packages to the index prematurely. + // 2. load all the packages of an org when a package of that org is requested. + public void loadPackage(PackageOrg packageOrg, PackageName packageName) { + updateIndexRepoCache(); + Path orgDir = indexDirectory.resolve(indexGitRepoName).resolve(packageOrg.value()); + Path packageFile = orgDir.resolve(packageName.value() + ".json"); + if (!Files.exists(packageFile)) { + return; + } + try { + List content = Files.readAllLines(packageFile); + Gson gson = new GsonBuilder() + .registerTypeAdapter(IndexPackage.class, new IndexPackageAdapter()) + .create(); + List packagesOfOrg = new ArrayList<>(); + for (String jsonString : content) { + packagesOfOrg.add(gson.fromJson(jsonString, IndexPackage.class)); + } + packageIndex.addPackage(packagesOfOrg); + } catch (IOException e) { + throw new ProjectException("Error reading index file: " + e.getMessage(), e); + } + } + + private boolean isIndexFetched() { + if (!indexDirectory.resolve(indexGitRepoName).toFile().isDirectory()) { + return false; + } + File gitDir = new File(indexDirectory.resolve(indexGitRepoName).toFile(), ".git"); + return gitDir.isDirectory(); + } + + private void fetchIndex() { + // TODO: add a progress bar + try { + Files.createDirectories(indexDirectory); + } catch (IOException e) { + throw new ProjectException("Error while creating the package index directory: " + e.getMessage()); + } + try (Git git = Git.cloneRepository() + .setURI(remoteRepoUrl) + .setDirectory(indexDirectory.resolve(indexGitRepoName).toFile()) + .setBranchesToClone(Arrays.asList("refs/heads/prod", "refs/heads/dev", "refs/heads/stage")) + .setCredentialsProvider(credentialsProvider) + .call()) { + git.checkout().setCreateBranch(true).setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) + .setStartPoint("origin/prod").setName("prod").call(); + git.checkout().setCreateBranch(true).setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) + .setStartPoint("origin/dev").setName("dev").call(); + git.checkout().setCreateBranch(true).setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) + .setStartPoint("origin/stage").setName("stage").call(); + } catch (GitAPIException e) { + throw new ProjectException("Error while cloning the package index repository: " + e.getMessage(), e); + } + } + + private void checkoutToBranch() { + Branch branch = Branch.PROD; + if (RepoUtils.SET_BALLERINA_STAGE_CENTRAL) { + branch = Branch.STAGE; + } else if (SET_BALLERINA_DEV_CENTRAL) { + branch = Branch.DEV; + } + try (Git git = Git.open(indexDirectory.resolve(indexGitRepoName).toFile())) { + git.checkout().setName(branch.branchName()).call(); + } catch (IOException | GitAPIException e) { + throw new ProjectException("Error while checking out to the " + branch + " branch: " + e.getMessage(), e); + } + } + + private void fetchIndexHead() { + // TODO: add a progress bar + try (Git git = Git.open(indexDirectory.resolve(indexGitRepoName).toFile())) { + git.pull().setCredentialsProvider(credentialsProvider).call(); + } catch (IOException | GitAPIException e) { + throw new ProjectException("Error while pulling the latest changes from the upstream: " + + e.getMessage(), e); + } + } + + // TODO: this implicitly assumes that the index always has these 3 branches. Need to discuss. + private enum Branch { + PROD("prod"), + DEV("dev"), + STAGE("stage"); + private final String branchName; + Branch(String branchName) { + this.branchName = branchName; + } + public String branchName() { + return branchName; + } + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/AbstractPackageRepository.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/AbstractPackageRepository.java index f9c59655922a..168361b81872 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/AbstractPackageRepository.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/AbstractPackageRepository.java @@ -17,7 +17,6 @@ */ package io.ballerina.projects.internal.repositories; -import io.ballerina.projects.DependencyGraph; import io.ballerina.projects.ModuleDescriptor; import io.ballerina.projects.PackageDescriptor; import io.ballerina.projects.PackageName; @@ -49,20 +48,14 @@ public abstract class AbstractPackageRepository implements PackageRepository { @Override - public Collection getPackageMetadata(Collection requests, - ResolutionOptions options) { - List descriptorSet = new ArrayList<>(); - for (ResolutionRequest request : requests) { - List versions = getCompatiblePackageVersions( - request.packageDescriptor(), request.packageLockingMode()); - PackageVersion latest = findLatest(versions); - if (latest != null) { - descriptorSet.add(createMetadataResponse(request, latest)); - } else { - descriptorSet.add(PackageMetadataResponse.createUnresolvedResponse(request)); - } + public PackageMetadataResponse getPackageMetadata(ResolutionRequest request, ResolutionOptions options) { + List versions = getCompatiblePackageVersions( + request.packageDescriptor(), request.packageLockingMode()); + PackageVersion latest = findLatest(versions); + if (latest != null) { + return createMetadataResponse(request, latest); } - return descriptorSet; + return PackageMetadataResponse.createUnresolvedResponse(request); } @Override @@ -80,9 +73,9 @@ protected abstract List getPackageVersions(PackageOrg org, PackageName name, PackageVersion version); - protected abstract DependencyGraph getDependencyGraph(PackageOrg org, - PackageName name, - PackageVersion version); + protected abstract Collection getDirectDependencies(PackageOrg org, + PackageName name, + PackageVersion version); public abstract boolean isPackageExists(PackageOrg org, PackageName name, @@ -178,9 +171,9 @@ private PackageMetadataResponse createMetadataResponse(ResolutionRequest resolut PackageDescriptor resolvedDescriptor = PackageDescriptor.from( resolutionRequest.orgName(), resolutionRequest.packageName(), latest, resolutionRequest.repositoryName().orElse(null)); - DependencyGraph dependencyGraph = getDependencyGraph(resolutionRequest.orgName(), + Collection directDependencies = getDirectDependencies(resolutionRequest.orgName(), resolutionRequest.packageName(), latest); - return PackageMetadataResponse.from(resolutionRequest, resolvedDescriptor, dependencyGraph); + return PackageMetadataResponse.from(resolutionRequest, resolvedDescriptor, directDependencies); } protected PackageVersion findLatest(List packageVersions) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/FileSystemRepository.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/FileSystemRepository.java index 79a236328616..c8b66f8260f0 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/FileSystemRepository.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/FileSystemRepository.java @@ -19,7 +19,6 @@ import com.github.zafarkhaja.semver.UnexpectedCharacterException; import com.github.zafarkhaja.semver.Version; -import io.ballerina.projects.DependencyGraph; import io.ballerina.projects.JvmTarget; import io.ballerina.projects.ModuleDescriptor; import io.ballerina.projects.Package; @@ -287,12 +286,13 @@ private boolean isCompatible(String pkgBalVer, String distBalVer) { } @Override - protected DependencyGraph getDependencyGraph(PackageOrg org, + protected Collection getDirectDependencies(PackageOrg org, PackageName name, PackageVersion version) { Path balaPath = getPackagePath(org.toString(), name.toString(), version.toString()); BalaFiles.DependencyGraphResult dependencyGraphResult = BalaFiles.createPackageDependencyGraph(balaPath); - return dependencyGraphResult.packageDependencyGraph(); + return dependencyGraphResult.packageDependencyGraph() + .getDirectDependencies(PackageDescriptor.from(org, name, version)); } @Override diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/MavenPackageRepository.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/MavenPackageRepository.java index 638fce6e91df..58b7a35beef5 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/MavenPackageRepository.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/MavenPackageRepository.java @@ -18,7 +18,6 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; -import io.ballerina.projects.DependencyGraph; import io.ballerina.projects.ModuleDescriptor; import io.ballerina.projects.Package; import io.ballerina.projects.PackageDescriptor; @@ -153,9 +152,9 @@ protected List getPackageVersions(PackageOrg org, PackageName na } @Override - protected DependencyGraph getDependencyGraph(PackageOrg org, PackageName name, - PackageVersion version) { - return this.fileSystemCache.getDependencyGraph(org, name, version); + protected Collection getDirectDependencies(PackageOrg org, PackageName name, + PackageVersion version) { + return this.fileSystemCache.getDirectDependencies(org, name, version); } @Override diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/RemotePackageRepository.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/RemotePackageRepository.java index 0e5a05f6fb48..074a2fdbf265 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/RemotePackageRepository.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/RemotePackageRepository.java @@ -1,6 +1,5 @@ package io.ballerina.projects.internal.repositories; -import io.ballerina.projects.DependencyGraph; import io.ballerina.projects.JvmTarget; import io.ballerina.projects.Package; import io.ballerina.projects.PackageDescriptor; @@ -8,6 +7,7 @@ import io.ballerina.projects.PackageOrg; import io.ballerina.projects.PackageVersion; import io.ballerina.projects.ProjectException; +import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.Settings; import io.ballerina.projects.environment.Environment; import io.ballerina.projects.environment.PackageLockingMode; @@ -18,6 +18,10 @@ import io.ballerina.projects.environment.ResolutionResponse; import io.ballerina.projects.internal.ImportModuleRequest; import io.ballerina.projects.internal.ImportModuleResponse; +import io.ballerina.projects.internal.index.IndexPackage; +import io.ballerina.projects.internal.index.PackageIndex; +import io.ballerina.projects.internal.index.PackageIndexBuilder; +import io.ballerina.projects.util.ProjectUtils; import org.ballerinalang.central.client.CentralAPIClient; import org.ballerinalang.central.client.CentralClientConstants; import org.ballerinalang.central.client.exceptions.CentralClientException; @@ -45,7 +49,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static io.ballerina.projects.DependencyGraph.DependencyGraphBuilder.getBuilder; import static io.ballerina.projects.util.ProjectUtils.getAccessTokenOfCLI; import static io.ballerina.projects.util.ProjectUtils.getLatest; import static io.ballerina.projects.util.ProjectUtils.initializeProxy; @@ -59,10 +62,15 @@ public class RemotePackageRepository implements PackageRepository { private final FileSystemRepository fileSystemRepo; private final CentralAPIClient client; + private final PackageIndex packageIndex; - public RemotePackageRepository(FileSystemRepository fileSystemRepo, CentralAPIClient client) { + private static final String ANY = "any"; + + public RemotePackageRepository(FileSystemRepository fileSystemRepo, CentralAPIClient client, + PackageIndex packageIndex) { this.fileSystemRepo = fileSystemRepo; this.client = client; + this.packageIndex = packageIndex; } public static RemotePackageRepository from(Environment environment, Path cacheDirectory, String repoUrl, @@ -79,7 +87,8 @@ public static RemotePackageRepository from(Environment environment, Path cacheDi settings.getCentral().getConnectTimeout(), settings.getCentral().getReadTimeout(), settings.getCentral().getWriteTimeout(), settings.getCentral().getCallTimeout(), settings.getCentral().getMaxRetries()); - return new RemotePackageRepository(fileSystemRepository, client); + PackageIndex packageIndex = new PackageIndexBuilder().build(); + return new RemotePackageRepository(fileSystemRepository, client, packageIndex); } public static RemotePackageRepository from(Environment environment, Path cacheDirectory, Settings settings) { @@ -108,7 +117,7 @@ public Optional getPackage(ResolutionRequest request, ResolutionOptions // If environment is online pull from central if (!options.offline()) { String supportedPlatform = Arrays.stream(JvmTarget.values()) - .map(target -> target.code()) + .map(JvmTarget::code) .collect(Collectors.joining(",")); try { this.client.pullPackage(orgName, packageName, version, packagePathInBalaCache, supportedPlatform, @@ -134,9 +143,7 @@ public Collection getPackageVersions(ResolutionRequest request, if (langRepoBuild != null) { return Collections.emptyList(); } - String orgName = request.orgName().value(); - String packageName = request.packageName().value(); - + // TODO: Can use the index instead. If offline, set offline flag in the index and find. // First, Get local versions Set packageVersions = new HashSet<>(fileSystemRepo.getPackageVersions(request, options)); @@ -145,22 +152,38 @@ public Collection getPackageVersions(ResolutionRequest request, return new ArrayList<>(packageVersions); } + List packagesFromIndex = this.packageIndex.getPackage(request.orgName(), request.packageName()); + if (packagesFromIndex != null) { + List distCompatiblePackages = filterDistributionCompatiblePackages(packagesFromIndex); + List platformCompatiblePackages = filterPackagesByPlatform(distCompatiblePackages); + List versionsFromIndex = platformCompatiblePackages.stream().map(IndexPackage::version) + .toList(); + packageVersions.addAll(versionsFromIndex); + return new ArrayList<>(packageVersions); + } + + // if the package is not in the index, we try to get the versions from the ballerina central, + // assuming it's a private package. + getPackageVersionsFromRemote(request, packageVersions); + return new ArrayList<>(packageVersions); + } + + private void getPackageVersionsFromRemote(ResolutionRequest request, Set packageVersions) { + String orgName = request.orgName().value(); + String packageName = request.packageName().value(); try { String supportedPlatform = Arrays.stream(JvmTarget.values()) - .map(target -> target.code()) + .map(JvmTarget::code) .collect(Collectors.joining(",")); for (String version : this.client.getPackageVersions(orgName, packageName, supportedPlatform, RepoUtils.getBallerinaVersion())) { packageVersions.add(PackageVersion.from(version)); } - } catch (ConnectionErrorException e) { // ignore connect to remote repo failure - return new ArrayList<>(packageVersions); } catch (CentralClientException e) { throw new ProjectException(e.getMessage()); } - return new ArrayList<>(packageVersions); } @Override @@ -177,44 +200,110 @@ public Collection getPackageNames(Collection index = getPackageNamesFromIndex(requests); + List unresolved = index.stream() + .filter(r -> r.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.UNRESOLVED)) + .map(ImportModuleResponse::importModuleRequest) + .toList(); + Collection remote = Collections.emptyList(); + if (!unresolved.isEmpty()) { + // Unresolved modules might be from the private packages. Let's try to resolve with the central API. + remote = getPackageNamesFromRemote(unresolved); + } + return mergeNameResolution(filesystem, index, remote); + } + + private List getPackageNamesFromIndex(Collection requests) { + List responses = new ArrayList<>(); + for (ImportModuleRequest request: requests) { + List indexPackages = packageIndex.getPackageContainingModule( + request.packageOrg(), request.moduleName()); + List distributionCompatiblePackages = filterDistributionCompatiblePackages(indexPackages); + if (distributionCompatiblePackages.isEmpty()) { + responses.add(ImportModuleResponse.createUnresolvedResponse(request)); + continue; + } + + // Try to find a match from the list of possible packages + Optional resolved = resolveModuleFromPossiblePackages( + request.possiblePackages(), distributionCompatiblePackages); + if (resolved.isPresent()) { + responses.add(new ImportModuleResponse(PackageDescriptor.from(resolved.get().org(), + resolved.get().name(), resolved.get().version()), request)); + continue; + } + + // If not found, find the latest version from the indexPackages. + resolved = findLatestInRange(distributionCompatiblePackages); + if (resolved.isPresent()) { + responses.add(new ImportModuleResponse(PackageDescriptor.from(resolved.get().org(), + resolved.get().name(), resolved.get().version()), request)); + continue; + } + + // If there is still no matching version, return unresolved response. + responses.add(ImportModuleResponse.createUnresolvedResponse(request)); + } + return responses; + } + + private Collection getPackageNamesFromRemote(Collection requests) { try { - List remote = new ArrayList<>(); PackageNameResolutionRequest resolutionRequest = toPackageNameResolutionRequest(requests); String supportedPlatform = Arrays.stream(JvmTarget.values()) - .map(target -> target.code()) + .map(JvmTarget::code) .collect(Collectors.joining(",")); PackageNameResolutionResponse response = this.client.resolvePackageNames(resolutionRequest, supportedPlatform, RepoUtils.getBallerinaVersion()); - remote.addAll(toImportModuleResponses(requests, response)); - - return mergeNameResolution(filesystem, remote); - } catch (ConnectionErrorException e) { - // ignore connect to remote repo failure - // TODO we need to add diagnostics for resolution errors + return new ArrayList<>(toImportModuleResponses(requests, response)); + } catch (ConnectionErrorException ignored) { + return new ArrayList<>(); } catch (CentralClientException e) { throw new ProjectException(e.getMessage()); } - return filesystem; } - private List mergeNameResolution(Collection filesystem, - Collection remote) { - return new ArrayList<>( - Stream.of(filesystem, remote) - .flatMap(Collection::stream).collect(Collectors.toMap( - ImportModuleResponse::importModuleRequest, Function.identity(), - (ImportModuleResponse x, ImportModuleResponse y) -> { - if (y.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.UNRESOLVED)) { - return x; - } else if (x.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.UNRESOLVED)) { - return y; - } else if (getLatest(x.packageDescriptor().version(), - y.packageDescriptor().version()).equals( - y.packageDescriptor().version())) { - return y; - } - return x; - })).values()); + private Optional resolveModuleFromPossiblePackages(List possiblePackages, + List indexPackages) { + PackageVersion resolvedVersion; + for (PackageDescriptor possiblePkg : possiblePackages) { + for (IndexPackage indexPkg: indexPackages) { + if (!possiblePkg.org().equals(indexPkg.org()) || !possiblePkg.name().equals(indexPkg.name())) { + continue; + } + resolvedVersion = possiblePkg.version() != null ? possiblePkg.version() : indexPkg.version(); + List pkgsInRange = filterPackagesInRange( + indexPackages, + Optional.ofNullable(resolvedVersion), + PackageLockingMode.MEDIUM); + Optional pkg = findLatestInRange(pkgsInRange); + if (pkg.isPresent()) { + return pkg; + } + } + } + return Optional.empty(); + } + + private PackageNameResolutionRequest toPackageNameResolutionRequest(Collection unresolved) { + PackageNameResolutionRequest request = new PackageNameResolutionRequest(); + for (ImportModuleRequest module : unresolved) { + if (module.possiblePackages().isEmpty()) { + request.addModule(module.packageOrg().value(), + module.moduleName()); + continue; + } + List possiblePackages = new ArrayList<>(); + for (PackageDescriptor possiblePackage : module.possiblePackages()) { + possiblePackages.add(new PackageNameResolutionRequest.Module.PossiblePackage( + possiblePackage.org().toString(), + possiblePackage.name().toString(), + possiblePackage.version().toString())); + } + request.addModule(module.packageOrg().value(), + module.moduleName(), possiblePackages, PackageResolutionRequest.Mode.MEDIUM); + } + return request; } private List toImportModuleResponses(Collection requests, @@ -239,194 +328,191 @@ private List toImportModuleResponses(Collection unresolved) { - PackageNameResolutionRequest request = new PackageNameResolutionRequest(); - for (ImportModuleRequest module : unresolved) { - if (module.possiblePackages().isEmpty()) { - request.addModule(module.packageOrg().value(), - module.moduleName()); - continue; - } - List possiblePackages = new ArrayList<>(); - for (PackageDescriptor possiblePackage : module.possiblePackages()) { - possiblePackages.add(new PackageNameResolutionRequest.Module.PossiblePackage( - possiblePackage.org().toString(), - possiblePackage.name().toString(), - possiblePackage.version().toString())); - } - request.addModule(module.packageOrg().value(), - module.moduleName(), possiblePackages, PackageResolutionRequest.Mode.MEDIUM); - } - return request; + private List mergeNameResolution(Collection filesystem, + Collection index, + Collection remote) { + return new ArrayList<>( + Stream.of(filesystem, index, remote) + .flatMap(Collection::stream).collect(Collectors.toMap( + ImportModuleResponse::importModuleRequest, Function.identity(), + (ImportModuleResponse x, ImportModuleResponse y) -> { + if (y.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.UNRESOLVED)) { + return x; + } else if (x.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.UNRESOLVED)) { + return y; + } else if (getLatest(x.packageDescriptor().version(), + y.packageDescriptor().version()).equals( + y.packageDescriptor().version())) { + return y; + } + return x; + })).values()); } @Override - public Collection getPackageMetadata(Collection requests, + public PackageMetadataResponse getPackageMetadata(ResolutionRequest request, ResolutionOptions options) { - if (requests.isEmpty()) { - return Collections.emptyList(); + // Is this wanted? + if (request == null) { + return null; } - // Resolve all the requests locally - Collection cachedPackages = fileSystemRepo.getPackageMetadata(requests, options); - List deprecatedPackages = new ArrayList<>(); - if (options.offline()) { - return cachedPackages; + packageIndex.setOffline(options.offline()); + + // If the locking mode is LOCKED or HARD, we should return the resolved package from the index. + if (request.packageLockingMode().equals(PackageLockingMode.LOCKED) + || request.packageLockingMode().equals(PackageLockingMode.HARD)) { + IndexPackage resolvedPackage = packageIndex.getVersion( + request.orgName(), + request.packageName(), + request.version().orElse(null)); + return createResolutionResponse(request, resolvedPackage); } - List updatedRequests = new ArrayList<>(requests); - // Remove the already resolved requests when the locking mode is hard - for (PackageMetadataResponse response : cachedPackages) { - if (response.packageLoadRequest().packageLockingMode().equals(PackageLockingMode.HARD) - && response.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.RESOLVED)) { - updatedRequests.remove(response.packageLoadRequest()); - } - if (response.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.RESOLVED)) { - Optional pkg = fileSystemRepo.getPackage(response.packageLoadRequest(), options); - if (pkg.isPresent() && pkg.get().descriptor().getDeprecated()) { - deprecatedPackages.add(response); - } + + // If the locking mode is MEDIUM or SOFT, we should return the latest compatible package from the index. + List indexPackages = packageIndex.getPackage(request.orgName(), request.packageName()); + if (indexPackages != null) { + indexPackages = filterDistributionCompatiblePackages(indexPackages); + indexPackages = filterDeprecatedPackages(indexPackages); + indexPackages = filterPackagesByPlatform(indexPackages); + indexPackages = filterStablePackages(indexPackages); + indexPackages = filterPackagesInRange(indexPackages, request.version(), request.packageLockingMode()); + Optional latest = findLatestInRange(indexPackages); + if (latest.isPresent()) { + return createResolutionResponse(request, latest.get()); } } - // Resolve the requests from remote repository if there are unresolved requests - if (!updatedRequests.isEmpty()) { - try { - PackageResolutionRequest packageResolutionRequest = toPackageResolutionRequest(updatedRequests); - Collection remotePackages = - fromPackageResolutionResponse(updatedRequests, packageResolutionRequest); - // Merge central requests and local requests - // Here we will pick the latest package from remote or local - return mergeResolution(remotePackages, cachedPackages, deprecatedPackages); - - } catch (ConnectionErrorException e) { - // ignore connect to remote repo failure - // TODO we need to add diagnostics for resolution errors - } catch (CentralClientException e) { - throw new ProjectException(e.getMessage()); - } + + // If not resolved, might be a private package. Let's try to resolve with the central API. + PackageResolutionRequest packageResolutionRequest = toPackageResolutionRequest(request); + try { + return fromPackageResolutionResponse(request, packageResolutionRequest); + } catch (CentralClientException e) { + throw new ProjectException(e.getMessage()); } - // Return cachedPackages when central requests are not performed - return cachedPackages; } - private Collection mergeResolution( - Collection remoteResolution, Collection filesystem, - List deprecatedPackages) { - List mergedResults = new ArrayList<>( - Stream.of(filesystem, remoteResolution) - .flatMap(Collection::stream).collect(Collectors.toMap( - PackageMetadataResponse::packageLoadRequest, Function.identity(), - (PackageMetadataResponse x, PackageMetadataResponse y) -> { - if (y.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.UNRESOLVED)) { - // filesystem response is resolved & remote response is unresolved - return x; - } else if (x.resolutionStatus().equals(ResolutionResponse.ResolutionStatus.UNRESOLVED)) { - // filesystem response is unresolved & remote response is resolved - return y; - } else if (x.resolvedDescriptor().version().equals(y.resolvedDescriptor().version())) { - // Both responses have the same version and there is a mismatch in deprecated status, - // we need to update the deprecated status in the file system repo - // to match the remote repo as it is the most up to date. - if (deprecatedPackages != null && y.resolvedDescriptor() != null && - deprecatedPackages.contains(x) ^ y.resolvedDescriptor().getDeprecated()) { - fileSystemRepo.updateDeprecatedStatusForPackage(y.resolvedDescriptor()); - } - return x; - } - // x not deprecate & y not deprecate - // - x is the latest : return x (this will not happen in real) - // - y is the latest : return y - // x not deprecated & y deprecated - // - x is the latest : outdated. return y - // - y is the latest : return y - // x deprecated & y not deprecated - // - x is the latest : outdated. return y - // - y is the latest : return y - // x deprecated & y deprecated - // - x is the latest : not possible - // - y is the latest : return y - - // If the equivalent package is available in the file system repo, - // try to update the deprecated status. - // Because if available in cache, it won't be pulled. - fileSystemRepo.updateDeprecatedStatusForPackage(y.resolvedDescriptor()); - return y; - })).values()); - return mergedResults; + private PackageMetadataResponse createResolutionResponse(ResolutionRequest request, IndexPackage resolvedPackage) { + if (resolvedPackage == null) { + return PackageMetadataResponse.createUnresolvedResponse(request); + } + if (ProjectUtils.isDistributionSupported(resolvedPackage.ballerinaVersion())) { + return PackageMetadataResponse.createUnresolvedResponse(request); + } + return PackageMetadataResponse.from(request, request.packageDescriptor(), resolvedPackage.dependencies()); + } + + private List filterDistributionCompatiblePackages(List indexPackages) { + return indexPackages.stream().filter(pkg -> ProjectUtils.isDistributionSupported(pkg.ballerinaVersion())) + .toList(); + } + + private List filterDeprecatedPackages(List indexPackages) { + List nonDepPkgs = indexPackages.stream().filter(pkg -> !pkg.isDeprecated()).toList(); + return nonDepPkgs.isEmpty() ? indexPackages : nonDepPkgs; + } + + private List filterPackagesByPlatform(List indexPackages) { + List supportedPlatforms = Arrays.stream(JvmTarget.values()).map(JvmTarget::code).toList(); + return indexPackages.stream().filter(pkg -> + ANY.equals(pkg.supportedPlatform()) || supportedPlatforms.contains(pkg.supportedPlatform())).toList(); + } + + private List filterStablePackages(List indexPackages) { + List stableVersions = indexPackages.stream().filter( + pkg -> !pkg.version().value().isPreReleaseVersion()).toList(); + return stableVersions.isEmpty() ? indexPackages : stableVersions; + } + + private List filterPackagesInRange(List indexPackages, + Optional refVersionOpt, + PackageLockingMode packageLockingMode) { + if (refVersionOpt.isEmpty()) { + return indexPackages; + } + SemanticVersion refVersion = refVersionOpt.get().value(); + return indexPackages.stream().filter(pkg -> { + SemanticVersion pkgVersion = pkg.version().value(); + return switch (packageLockingMode) { + case LATEST -> true; + case SOFT -> pkgVersion.major() == refVersion.major(); + case MEDIUM -> pkgVersion.major() == refVersion.major() && pkgVersion.minor() == refVersion.minor(); + case HARD, LOCKED, INVALID -> // We should not reach here because we handled locked and hard before. + throw new IllegalStateException("Unexpected value: " + packageLockingMode); + }; + }).toList(); } - private Collection fromPackageResolutionResponse( - Collection packageLoadRequests, PackageResolutionRequest packageResolutionRequest) + private Optional findLatestInRange(List indexPackages) { + if (indexPackages.isEmpty()) { + return Optional.empty(); + } + IndexPackage latest = indexPackages.get(0); + for (IndexPackage indexPackage : indexPackages) { + if (indexPackage.version().value().greaterThan(latest.version().value())) { + latest = indexPackage; + } + } + return Optional.of(latest); + } + + + + // Each dependency will contain only the direct dependencies of the package after the indexing implementation. + private PackageMetadataResponse fromPackageResolutionResponse( + ResolutionRequest resolutionRequest, PackageResolutionRequest packageResolutionRequest) throws CentralClientException { - List response = new ArrayList<>(); - Set resolvedRequests = new HashSet<>(); String supportedPlatform = Arrays.stream(JvmTarget.values()) - .map(target -> target.code()) + .map(JvmTarget::code) .collect(Collectors.joining(",")); - PackageResolutionResponse packageResolutionResponse = client.resolveDependencies( + PackageResolutionResponse packageResolutionResponse = this.client.resolveDependencies( packageResolutionRequest, supportedPlatform, RepoUtils.getBallerinaVersion()); - for (ResolutionRequest resolutionRequest : packageLoadRequests) { - if (resolvedRequests.contains(resolutionRequest)) { - continue; - } - // find response from server - // checked in resolved group - Optional match = packageResolutionResponse.resolved().stream() - .filter(p -> p.name().equals(resolutionRequest.packageName().value()) && - p.org().equals(resolutionRequest.orgName().value())).findFirst(); - // If we found a match we will add it to response - if (match.isPresent()) { - PackageVersion version = PackageVersion.from(match.get().version()); - DependencyGraph dependencies = createPackageDependencyGraph(match.get()); - PackageDescriptor packageDescriptor = PackageDescriptor.from(resolutionRequest.orgName(), - resolutionRequest.packageName(), - version, match.get().getDeprecated(), match.get().getDeprecateMessage()); - PackageMetadataResponse responseDescriptor = PackageMetadataResponse.from(resolutionRequest, - packageDescriptor, - dependencies); - response.add(responseDescriptor); - resolvedRequests.add(resolutionRequest); - } else { - // If the package is not in resolved for all jvm platforms we assume the package is unresolved - response.add(PackageMetadataResponse.createUnresolvedResponse(resolutionRequest)); - } + // find response from server + // checked in resolved group + Optional match = packageResolutionResponse.resolved().stream() + .filter(p -> p.name().equals(resolutionRequest.packageName().value()) && + p.org().equals(resolutionRequest.orgName().value())).findFirst(); + // If we found a match we will add it to response + if (match.isPresent()) { + PackageVersion version = PackageVersion.from(match.get().version()); + Collection dependencies = getDirectDependencies(match.get()); + PackageDescriptor packageDescriptor = PackageDescriptor.from(resolutionRequest.orgName(), + resolutionRequest.packageName(), + version, match.get().getDeprecated(), match.get().getDeprecateMessage()); + return PackageMetadataResponse.from(resolutionRequest, + packageDescriptor, + dependencies); } - - return response; + // If the package is not in resolved for all jvm platforms we assume the package is unresolved + return PackageMetadataResponse.createUnresolvedResponse(resolutionRequest); } - private static DependencyGraph createPackageDependencyGraph( - PackageResolutionResponse.Package aPackage) { - DependencyGraph.DependencyGraphBuilder graphBuilder = getBuilder(); - + private static Collection getDirectDependencies(PackageResolutionResponse.Package aPackage) { + List dependencies = new ArrayList<>(); for (PackageResolutionResponse.Dependency dependency : aPackage.dependencyGraph()) { + if (aPackage.org().equals(dependency.org()) && aPackage.name().equals(dependency.name())) { + continue; + } PackageDescriptor pkg = PackageDescriptor.from(PackageOrg.from(dependency.org()), PackageName.from(dependency.name()), PackageVersion.from(dependency.version())); - Set dependentPackages = new HashSet<>(); - for (PackageResolutionResponse.Dependency dependencyPkg : dependency.dependencies()) { - dependentPackages.add(PackageDescriptor.from(PackageOrg.from(dependencyPkg.org()), - PackageName.from(dependencyPkg.name()), - PackageVersion.from(dependencyPkg.version()))); - } - graphBuilder.addDependencies(pkg, dependentPackages); + dependencies.add(pkg); } - - return graphBuilder.build(); + return dependencies; } - private PackageResolutionRequest toPackageResolutionRequest(Collection resolutionRequests) { + private PackageResolutionRequest toPackageResolutionRequest(ResolutionRequest resolutionRequest) { PackageResolutionRequest packageResolutionRequest = new PackageResolutionRequest(); - for (ResolutionRequest resolutionRequest : resolutionRequests) { - PackageResolutionRequest.Mode mode = switch (resolutionRequest.packageLockingMode()) { - case HARD -> PackageResolutionRequest.Mode.HARD; - case MEDIUM -> PackageResolutionRequest.Mode.MEDIUM; - case SOFT -> PackageResolutionRequest.Mode.SOFT; - }; - String version = resolutionRequest.version().map(v -> v.value().toString()).orElse(""); - packageResolutionRequest.addPackage(resolutionRequest.orgName().value(), - resolutionRequest.packageName().value(), - version, - mode); - } + PackageResolutionRequest.Mode mode = switch (resolutionRequest.packageLockingMode()) { + case HARD, LOCKED -> PackageResolutionRequest.Mode.HARD; + case MEDIUM -> PackageResolutionRequest.Mode.MEDIUM; + case SOFT, LATEST -> PackageResolutionRequest.Mode.SOFT; + default -> throw new IllegalStateException("Unexpected value: " + resolutionRequest.packageLockingMode()); + }; + String version = resolutionRequest.version().map(v -> v.value().toString()).orElse(""); + packageResolutionRequest.addPackage(resolutionRequest.orgName().value(), + resolutionRequest.packageName().value(), + version, + mode); return packageResolutionRequest; } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java index 798e1725b3c1..2010343eec12 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java @@ -113,6 +113,7 @@ private ProjectConstants() {} public static final String REPO_BALA_DIR_NAME = TARGET_BALA_DIR_NAME; public static final String REPO_CACHE_DIR_NAME = "cache"; public static final String REPO_BIR_CACHE_NAME = "bir"; + public static final String INDEX_DIR_NAME = "index"; // Test framework related constants public static final String TEST_RUNTIME_JAR_PREFIX = "testerina-runtime-"; @@ -153,4 +154,7 @@ private ProjectConstants() {} public static final String OFFLINE_FLAG = "--offline"; public static final String REPOSITORY_FLAG = "--repository"; public static final String WILD_CARD = "*"; + + public static final String SL_ALPHA = "slalpha"; + public static final String SL_BETA = "slbeta"; } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java index 3ede3002f061..28e60835b2ac 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java @@ -114,6 +114,8 @@ import static io.ballerina.projects.util.ProjectConstants.JACOCO_REPORT_JAR; import static io.ballerina.projects.util.ProjectConstants.LIB_DIR; import static io.ballerina.projects.util.ProjectConstants.RESOURCE_DIR_NAME; +import static io.ballerina.projects.util.ProjectConstants.SL_ALPHA; +import static io.ballerina.projects.util.ProjectConstants.SL_BETA; import static io.ballerina.projects.util.ProjectConstants.TARGET_DIR_NAME; import static io.ballerina.projects.util.ProjectConstants.TEST_CORE_JAR_PREFIX; import static io.ballerina.projects.util.ProjectConstants.TEST_RUNTIME_JAR_PREFIX; @@ -1491,4 +1493,34 @@ public static boolean containsDefaultModuleService(Package pkg) { } return false; } + + /** + * Check whether the package is supported by the currently installed distribution. + * + * @param packageBallerinaVersionStr The ballerina version of the package + * @return True if the package is supported by the distribution, False otherwise + */ + public static boolean isDistributionSupported(String packageBallerinaVersionStr) { + SemanticVersion distributionVersion = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()); + if (isValidSwanLakeGAVersion(packageBallerinaVersionStr)) { + SemanticVersion packageBallerinaVersion = SemanticVersion.from(packageBallerinaVersionStr); + return packageBallerinaVersion.lessThanOrEqualTo(distributionVersion); + } + return packageBallerinaVersionStr.startsWith(SL_ALPHA) || packageBallerinaVersionStr.startsWith(SL_BETA); + } + + /** + * Check if the given version addheres to Ballerina SwanLake distribution versioning. + * + * @param ballerinaVersion Ballerina version + * @return True if the version is a valid SwanLake GA version, False otherwise + */ + public static boolean isValidSwanLakeGAVersion(String ballerinaVersion) { + try { + SemanticVersion.from(ballerinaVersion); + return ballerinaVersion.indexOf(".") == 4; + } catch (ProjectException ignored) { + return false; + } + } } diff --git a/compiler/ballerina-lang/src/main/java/module-info.java b/compiler/ballerina-lang/src/main/java/module-info.java index 0c3b23d84fc3..2903146755f7 100644 --- a/compiler/ballerina-lang/src/main/java/module-info.java +++ b/compiler/ballerina-lang/src/main/java/module-info.java @@ -5,8 +5,10 @@ uses org.ballerinalang.compiler.plugins.CompilerPlugin; uses org.ballerinalang.spi.EmbeddedExecutor; requires java.compiler; + requires com.google.common; requires com.google.gson; requires java.xml; + requires org.eclipse.jgit; requires org.objectweb.asm; requires io.ballerina.runtime; requires io.netty.buffer; diff --git a/distribution/zip/jballerina-tools/build.gradle b/distribution/zip/jballerina-tools/build.gradle index 9e77ccabf736..a0ae745b7a11 100644 --- a/distribution/zip/jballerina-tools/build.gradle +++ b/distribution/zip/jballerina-tools/build.gradle @@ -89,6 +89,7 @@ dependencies { dist libs.ow2.asm.tree dist libs.ow2.asm.util dist libs.jackson.datatype.jsr310 + dist libs.jgit // Following dependencies are required for lang-server dist libs.sqlite.jdbc; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 300d2997fa67..168ca64e0d64 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,6 +72,7 @@ javaxTransactionApiVersion="1.3" javaxWsRsApiVersion="2.1.1" jetbrainsKotlinStdlibCommonVersion="1.6.0" jetbrainsKotlinStdlibVersion="1.6.0" +jgitVersion="6.6.1.202309021850-r" jknackHandlebarsVersion="4.0.6" jlineVersion="3.25.0" jsonUnitAssertJVersion="2.28.0" @@ -203,6 +204,7 @@ javax-transaction-api = { module = "javax.transaction:javax.transaction-api", ve javax-ws-rs-api = { module = "javax.ws.rs:javax.ws.rs-api", version.ref = "javaxWsRsApiVersion"} jetbrains-kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "jetbrainsKotlinStdlibVersion"} jetbrains-kotlin-stdlib-common = { module = "org.jetbrains.kotlin:kotlin-stdlib-common", version.ref = "jetbrainsKotlinStdlibCommonVersion"} +jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "jgitVersion"} jknack-handlebars = { module = "com.github.jknack:handlebars", version.ref = "jknackHandlebarsVersion"} jline = { module = "org.jline:jline", version.ref = "jlineVersion"} json-unit-assertj = { module = "net.javacrumbs.json-unit:json-unit-assertj", version.ref = "jsonUnitAssertJVersion"} From b9410265c65fc880a0cedb62b089bcd86d0fb88b Mon Sep 17 00:00:00 2001 From: gayaldassanayake Date: Thu, 30 Jan 2025 07:43:26 +0530 Subject: [PATCH 6/6] Introduce --update policy and remove sticky --- .../io/ballerina/cli/cmd/BuildCommand.java | 14 ++- .../io/ballerina/cli/cmd/CommandUtil.java | 2 +- .../io/ballerina/cli/cmd/GraphCommand.java | 15 ++- .../io/ballerina/cli/cmd/PackCommand.java | 14 ++- .../io/ballerina/cli/cmd/PullCommand.java | 18 ++- .../java/io/ballerina/cli/cmd/RunCommand.java | 15 ++- .../io/ballerina/cli/cmd/TestCommand.java | 14 ++- .../io/ballerina/cli/task/CompileTask.java | 5 +- .../cli/cmd/RunBuildToolsTaskTest.java | 3 +- .../io/ballerina/projects/BuildOptions.java | 12 +- .../projects/BuildToolResolution.java | 7 +- .../projects/CompilationOptions.java | 28 +++-- .../java/io/ballerina/projects/Package.java | 8 +- .../ballerina/projects/PackageResolution.java | 100 +++++++---------- .../environment/ResolutionOptions.java | 74 +++++++----- .../internal/LockingModeResolver.java | 106 ------------------ .../projects/internal/ManifestBuilder.java | 17 ++- .../internal/PackageLockingModeMatrix.java | 45 ++++++++ ... PackageLockingModeResolutionOptions.java} | 15 +-- .../internal/PackageLockingModeResolver.java | 93 +++++++++++++++ .../projects/internal/ResolutionEngine.java | 89 +++++++++------ .../environment/DefaultPackageResolver.java | 3 +- .../projects/internal/index/IndexPackage.java | 4 + .../internal/index/PackageIndexUpdater.java | 15 ++- .../repositories/RemotePackageRepository.java | 7 +- .../projects/util/DependencyUtils.java | 3 +- .../projects/util/ProjectConstants.java | 3 +- .../ballerina/projects/util/ProjectUtils.java | 30 +---- .../compiler/CompilerOptionName.java | 2 +- .../RemotePackageRepositoryTests.java | 7 +- .../internal/DefaultPackageRepository.java | 9 +- .../internal/PackageResolutionTestCase.java | 21 ++-- .../resolution/packages/internal/Utils.java | 27 +++-- .../command-completion/command-completion.csv | 6 +- .../workspace/BallerinaWorkspaceManager.java | 3 +- .../semver/checker/util/PackageUtils.java | 3 +- 36 files changed, 466 insertions(+), 371 deletions(-) delete mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeMatrix.java rename compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/{LockingModeResolutionOptions.java => PackageLockingModeResolutionOptions.java} (79%) create mode 100644 compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeResolver.java diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/BuildCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/BuildCommand.java index 46b63e0cbd84..b086862620d2 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/BuildCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/BuildCommand.java @@ -32,6 +32,7 @@ import io.ballerina.projects.ProjectException; import io.ballerina.projects.directory.BuildProject; import io.ballerina.projects.directory.SingleFileProject; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.util.ProjectConstants; import org.wso2.ballerinalang.util.RepoUtils; import picocli.CommandLine; @@ -177,8 +178,11 @@ public BuildCommand() { @CommandLine.Option(names = "--dump-build-time", description = "calculate and dump build time", hidden = true) private Boolean dumpBuildTime; - @CommandLine.Option(names = "--sticky", description = "stick to exact versions locked (if exists)") - private Boolean sticky; + @CommandLine.Option( + names = "--update-policy", + description = "update policy for dependency resolution. Options: ${COMPLETION-CANDIDATES}", + defaultValue = "SOFT") + private UpdatePolicy updatePolicy; @CommandLine.Option(names = "--target-dir", description = "target directory path") private Path targetDir; @@ -219,8 +223,8 @@ public void execute() { return; } - if (sticky == null) { - sticky = false; + if (updatePolicy == null) { + updatePolicy = UpdatePolicy.SOFT; } // load project @@ -317,7 +321,7 @@ private BuildOptions constructBuildOptions() { .setDumpRawGraphs(dumpRawGraphs) .setListConflictedClasses(listConflictedClasses) .setDumpBuildTime(dumpBuildTime) - .setSticky(sticky) + .setUpdatePolicy(updatePolicy) .setConfigSchemaGen(configSchemaGen) .setExportOpenAPI(exportOpenAPI) .setExportComponentModel(exportComponentModel) diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/CommandUtil.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/CommandUtil.java index 4612aa39de6c..d81015d9abfa 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/CommandUtil.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/CommandUtil.java @@ -1200,7 +1200,7 @@ static String getLatestVersion(List versions) { * @param orgName org name of the dependent package * @param packageName name of the dependent package * @param version version of the dependent package - * @param buildOptions build options {sticky, offline} + * @param buildOptions build options * @return true if the dependent package compilation has errors */ static boolean pullDependencyPackages(String orgName, String packageName, String version, diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/GraphCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/GraphCommand.java index 310271fadfa0..761260438914 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/GraphCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/GraphCommand.java @@ -30,6 +30,7 @@ import io.ballerina.projects.ProjectException; import io.ballerina.projects.directory.BuildProject; import io.ballerina.projects.directory.SingleFileProject; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.util.ProjectConstants; import org.wso2.ballerinalang.util.RepoUtils; import picocli.CommandLine; @@ -65,9 +66,9 @@ public class GraphCommand implements BLauncherCmd { "dependencies.") private boolean offline; - @CommandLine.Option(names = "--sticky", description = "stick to exact versions locked (if exists)", - defaultValue = "false") - private boolean sticky; + @CommandLine.Option(names = "--update-policy", description = "update policy for dependency resolution", + defaultValue = "SOFT") + private UpdatePolicy updatePolicy; public GraphCommand() { this.projectPath = Path.of(System.getProperty(ProjectConstants.USER_DIR)); @@ -91,6 +92,10 @@ public void execute() { return; } + if (updatePolicy == null) { + updatePolicy = UpdatePolicy.SOFT; + } + try { loadProject(); } catch (ProjectException e) { @@ -152,7 +157,7 @@ private BuildOptions constructBuildOptions() { .setDumpGraph(dumpGraph) .setDumpRawGraphs(this.dumpRawGraphs) .setOffline(this.offline) - .setSticky(this.sticky); + .setUpdatePolicy(this.updatePolicy); return buildOptionsBuilder.build(); } @@ -173,7 +178,7 @@ public void printLongDesc(StringBuilder out) { @Override public void printUsage(StringBuilder out) { - out.append(" bal graph [--dump-raw-graph] [--offline] [--sticky] \\n\" +\n" + + out.append(" bal graph [--dump-raw-graph] [--offline] [--update-policy] \\n\" +\n" + " \" []"); } diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PackCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PackCommand.java index 8e326dd813e2..d68f33d02c73 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PackCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PackCommand.java @@ -14,6 +14,7 @@ import io.ballerina.projects.Project; import io.ballerina.projects.ProjectException; import io.ballerina.projects.directory.BuildProject; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.ProjectDiagnosticErrorCode; import io.ballerina.projects.util.ProjectConstants; import io.ballerina.projects.util.ProjectUtils; @@ -75,8 +76,11 @@ public class PackCommand implements BLauncherCmd { @CommandLine.Option(names = "--dump-build-time", hidden = true, description = "calculate and dump build time") private Boolean dumpBuildTime; - @CommandLine.Option(names = "--sticky", description = "stick to exact versions locked (if exists)") - private Boolean sticky; + @CommandLine.Option( + names = "--update-policy", + description = "update policy for dependency resolution. Options: ${COMPLETION-CANDIDATES}", + defaultValue = "SOFT") + private UpdatePolicy updatePolicy; @CommandLine.Option(names = "--target-dir", description = "target directory path") private Path targetDir; @@ -152,8 +156,8 @@ public void execute() { Project project; - if (sticky == null) { - sticky = false; + if (updatePolicy == null) { + updatePolicy = UpdatePolicy.SOFT; } BuildOptions buildOptions = constructBuildOptions(); @@ -284,7 +288,7 @@ private BuildOptions constructBuildOptions() { .setDumpGraph(dumpGraph) .setDumpRawGraphs(dumpRawGraphs) .setDumpBuildTime(dumpBuildTime) - .setSticky(sticky) + .setUpdatePolicy(updatePolicy) .setConfigSchemaGen(configSchemaGen) .setEnableCache(enableCache) .disableSyntaxTreeCaching(disableSyntaxTreeCaching) diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PullCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PullCommand.java index 07be37fcec5f..a89912b0db50 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PullCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/PullCommand.java @@ -26,11 +26,11 @@ import io.ballerina.projects.ProjectException; import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.Settings; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.model.Proxy; import io.ballerina.projects.internal.model.Repository; import io.ballerina.projects.util.ProjectConstants; import io.ballerina.projects.util.ProjectUtils; -import org.apache.commons.io.FileUtils; import org.ballerinalang.central.client.CentralAPIClient; import org.ballerinalang.central.client.CentralClientConstants; import org.ballerinalang.central.client.exceptions.CentralClientException; @@ -90,8 +90,11 @@ public class PullCommand implements BLauncherCmd { @CommandLine.Option(names = "--repository") private String repositoryName; - @CommandLine.Option(names = "--sticky", hidden = true, defaultValue = "true") - private boolean sticky; + @CommandLine.Option( + names = "--update-policy", + description = "update policy for dependency resolution. Options: ${COMPLETION-CANDIDATES}", + defaultValue = "SOFT") + private UpdatePolicy updatePolicy; @CommandLine.Option(names = "--offline", hidden = true) private boolean offline; @@ -136,6 +139,10 @@ public void execute() { System.setProperty(CentralClientConstants.ENABLE_OUTPUT_STREAM, "true"); + if (updatePolicy == null) { + updatePolicy = UpdatePolicy.SOFT; + } + String resourceName = argList.get(0); String orgName; String packageName; @@ -324,7 +331,10 @@ private void pullFromMavenRepo(Settings settings, String orgName, String package private boolean resolveDependencies(String orgName, String packageName, String version) { CommandUtil.setPrintStream(errStream); try { - BuildOptions buildOptions = BuildOptions.builder().setSticky(sticky).setOffline(offline).build(); + BuildOptions buildOptions = BuildOptions.builder() + .setUpdatePolicy(updatePolicy) + .setOffline(offline) + .build(); boolean hasCompilationErrors = CommandUtil.pullDependencyPackages( orgName, packageName, version, buildOptions, repositoryName); if (hasCompilationErrors) { diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/RunCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/RunCommand.java index 14010c596033..ba2766b18d05 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/RunCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/RunCommand.java @@ -36,6 +36,7 @@ import io.ballerina.projects.ProjectKind; import io.ballerina.projects.directory.BuildProject; import io.ballerina.projects.directory.SingleFileProject; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.model.Target; import io.ballerina.projects.util.ProjectConstants; import io.ballerina.projects.util.ProjectUtils; @@ -52,6 +53,7 @@ import static io.ballerina.cli.cmd.Constants.RUN_COMMAND; import static io.ballerina.cli.launcher.LauncherUtils.createLauncherException; +import static io.ballerina.projects.environment.UpdatePolicy.SOFT; import static io.ballerina.projects.util.ProjectUtils.isProjectUpdated; import static io.ballerina.runtime.api.constants.RuntimeConstants.SYSTEM_PROP_BAL_DEBUG; @@ -100,8 +102,11 @@ public class RunCommand implements BLauncherCmd { "executable when run is used with a source file or a module.") private Boolean remoteManagement; - @CommandLine.Option(names = "--sticky", description = "stick to exact versions locked (if exists)") - private Boolean sticky; + @CommandLine.Option( + names = "--update-policy", + description = "update policy for dependency resolution. Options: ${COMPLETION-CANDIDATES}", + defaultValue = "SOFT") + private UpdatePolicy updatePolicy; @CommandLine.Option(names = "--dump-graph", description = "Print the dependency graph.", hidden = true) private boolean dumpGraph; @@ -213,8 +218,8 @@ public void execute() { } } - if (sticky == null) { - sticky = false; + if (updatePolicy == null) { + updatePolicy = SOFT; } if (this.watch) { @@ -346,7 +351,7 @@ private BuildOptions constructBuildOptions() { .setTestReport(false) .setObservabilityIncluded(observabilityIncluded) .setRemoteManagement(remoteManagement) - .setSticky(sticky) + .setUpdatePolicy(updatePolicy) .setDumpGraph(dumpGraph) .setDumpRawGraphs(dumpRawGraphs) .setConfigSchemaGen(configSchemaGen) diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/TestCommand.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/TestCommand.java index 8240f19b876b..25e41922bc56 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/TestCommand.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/cmd/TestCommand.java @@ -36,6 +36,7 @@ import io.ballerina.projects.ProjectException; import io.ballerina.projects.directory.BuildProject; import io.ballerina.projects.directory.SingleFileProject; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.util.ProjectConstants; import picocli.CommandLine; @@ -182,8 +183,11 @@ public TestCommand() { @CommandLine.Option(names = "--dump-build-time", description = "calculate and dump build time", hidden = true) private Boolean dumpBuildTime; - @CommandLine.Option(names = "--sticky", description = "stick to exact versions locked (if exists)") - private Boolean sticky; + @CommandLine.Option( + names = "--update-policy", + description = "update policy for dependency resolution. Options: ${COMPLETION-CANDIDATES}", + defaultValue = "SOFT") + private UpdatePolicy updatePolicy; @CommandLine.Option(names = "--target-dir", description = "target directory path") private Path targetDir; @@ -247,8 +251,8 @@ public void execute() { } } - if (sticky == null) { - sticky = false; + if (updatePolicy == null) { + updatePolicy = UpdatePolicy.SOFT; } if (isParallelExecution) { this.outStream.println("WARNING: Running tests in parallel is an experimental feature"); @@ -420,7 +424,7 @@ private BuildOptions constructBuildOptions() { .setTestReport(testReport) .setObservabilityIncluded(observabilityIncluded) .setDumpBuildTime(dumpBuildTime) - .setSticky(sticky) + .setUpdatePolicy(updatePolicy) .setCloud(cloud) .setDumpGraph(dumpGraph) .setDumpRawGraphs(dumpRawGraphs) diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/CompileTask.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/CompileTask.java index 9940a637e701..07de62104f94 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/CompileTask.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/CompileTask.java @@ -33,6 +33,7 @@ import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.directory.SingleFileProject; import io.ballerina.projects.environment.ResolutionOptions; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.PackageDiagnostic; import io.ballerina.projects.internal.ProjectDiagnosticErrorCode; import io.ballerina.projects.util.ProjectUtils; @@ -297,8 +298,8 @@ private void printWarningForHigherDistribution(Project project) { warning = "Detected an attempt to compile this package using Swan Lake Update " + currentVersionForDiagnostic + ". However, this package was built using Swan Lake Update " + prevVersionForDiagnostic + "."; - if (project.buildOptions().sticky()) { - warning += "\nHINT: Execute the bal command with --sticky=false"; + if (project.buildOptions().updatePolicy().equals(UpdatePolicy.LOCKED)) { + warning += "\nHINT: Execute the bal command with --update-policy=SOFT/MEDIUM/HARD"; } else { warning += " To ensure compatibility, the Dependencies.toml file will be updated with the " + "latest versions that are compatible with Update " + currentVersionForDiagnostic + "."; diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunBuildToolsTaskTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunBuildToolsTaskTest.java index 80f7efef4e41..d92a14b482c5 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunBuildToolsTaskTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/RunBuildToolsTaskTest.java @@ -23,6 +23,7 @@ import io.ballerina.projects.BuildOptions; import io.ballerina.projects.Project; import io.ballerina.projects.directory.BuildProject; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.util.BuildToolUtils; import org.ballerinalang.test.BCompileUtil; import org.mockito.MockedStatic; @@ -100,7 +101,7 @@ public void testOfflineToolResolution(String projectName, String outputFileName, throws IOException { Path projectPath = buildToolResources.resolve(projectName); Project project = BuildProject.load(projectPath, - BuildOptions.builder().setOffline(true).setSticky(sticky).build()); + BuildOptions.builder().setOffline(true).setUpdatePolicy(UpdatePolicy.HARD).build()); // TODO RunBuildToolsTask runBuildToolsTask = new RunBuildToolsTask(printStream); try (MockedStatic repoUtils = Mockito.mockStatic( BuildToolUtils.class, Mockito.CALLS_REAL_METHODS)) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildOptions.java index 709140908558..9e4de3af2cc1 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildOptions.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildOptions.java @@ -17,6 +17,8 @@ */ package io.ballerina.projects; +import io.ballerina.projects.environment.UpdatePolicy; + import java.util.Objects; /** @@ -75,8 +77,8 @@ public boolean offlineBuild() { return this.compilationOptions.offlineBuild(); } - public boolean sticky() { - return this.compilationOptions.sticky(); + public UpdatePolicy updatePolicy() { + return this.compilationOptions.updatePolicy(); } public boolean disableSyntaxTree() { @@ -206,7 +208,7 @@ public BuildOptions acceptTheirs(BuildOptions theirOptions) { buildOptionsBuilder.setDumpRawGraphs(compilationOptions.dumpRawGraphs); buildOptionsBuilder.setCloud(compilationOptions.cloud); buildOptionsBuilder.setListConflictedClasses(compilationOptions.listConflictedClasses); - buildOptionsBuilder.setSticky(compilationOptions.sticky); + buildOptionsBuilder.setUpdatePolicy(compilationOptions.updatePolicy); buildOptionsBuilder.setConfigSchemaGen(compilationOptions.configSchemaGen); buildOptionsBuilder.setExportOpenAPI(compilationOptions.exportOpenAPI); buildOptionsBuilder.setExportComponentModel(compilationOptions.exportComponentModel); @@ -314,8 +316,8 @@ public BuildOptionsBuilder setSkipTests(Boolean value) { return this; } - public BuildOptionsBuilder setSticky(Boolean value) { - compilationOptionsBuilder.setSticky(value); + public BuildOptionsBuilder setUpdatePolicy(UpdatePolicy updatePolicy) { + compilationOptionsBuilder.setUpdatePolicy(updatePolicy); return this; } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildToolResolution.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildToolResolution.java index c6078927b146..1825120762f6 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildToolResolution.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/BuildToolResolution.java @@ -22,8 +22,8 @@ import io.ballerina.projects.buildtools.ToolContext; import io.ballerina.projects.environment.PackageLockingMode; import io.ballerina.projects.environment.ToolResolutionRequest; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.util.BuildToolUtils; -import io.ballerina.projects.util.ProjectUtils; import io.ballerina.toml.semantic.diagnostics.TomlDiagnostic; import io.ballerina.toml.semantic.diagnostics.TomlNodeLocation; import io.ballerina.tools.diagnostics.Diagnostic; @@ -144,8 +144,9 @@ private List resolveToolVersions(Project project, List unr return getToolResolutionResponse(toolResolutionRequest); } - private PackageLockingMode getPackageLockingMode(Project project) { - boolean sticky = ProjectUtils.getSticky(project); + private PackageLockingMode getPackageLockingMode(Project project) { // TODO: redo this + boolean sticky = project.buildOptions().updatePolicy().equals(UpdatePolicy.HARD) + || project.buildOptions().updatePolicy().equals(UpdatePolicy.LOCKED); // TODO: temp fix to avoid error. Fix this // new project if (project.currentPackage().dependenciesToml().isEmpty()) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilationOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilationOptions.java index 31b5a927fa2e..e66139f902b7 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilationOptions.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/CompilationOptions.java @@ -17,6 +17,10 @@ */ package io.ballerina.projects; +import io.ballerina.projects.environment.UpdatePolicy; + +import java.util.Objects; + /** * The class {@code CompilationOptions} holds various Ballerina compilation options. * @@ -29,7 +33,7 @@ public class CompilationOptions { Boolean dumpBirFile; String cloud; Boolean listConflictedClasses; - Boolean sticky; + UpdatePolicy updatePolicy; Boolean dumpGraph; Boolean dumpRawGraphs; Boolean withCodeGenerators; @@ -43,7 +47,7 @@ public class CompilationOptions { Boolean optimizeDependencyCompilation; CompilationOptions(Boolean offlineBuild, Boolean observabilityIncluded, Boolean dumpBir, - Boolean dumpBirFile, String cloud, Boolean listConflictedClasses, Boolean sticky, + Boolean dumpBirFile, String cloud, Boolean listConflictedClasses, UpdatePolicy updatePolicy, Boolean dumpGraph, Boolean dumpRawGraphs, Boolean withCodeGenerators, Boolean withCodeModifiers, Boolean configSchemaGen, Boolean exportOpenAPI, Boolean exportComponentModel, Boolean enableCache, Boolean disableSyntaxTree, @@ -54,7 +58,7 @@ public class CompilationOptions { this.dumpBirFile = dumpBirFile; this.cloud = cloud; this.listConflictedClasses = listConflictedClasses; - this.sticky = sticky; + this.updatePolicy = updatePolicy; this.dumpGraph = dumpGraph; this.dumpRawGraphs = dumpRawGraphs; this.withCodeGenerators = withCodeGenerators; @@ -72,8 +76,8 @@ public boolean offlineBuild() { return toBooleanDefaultIfNull(this.offlineBuild); } - boolean sticky() { - return toBooleanTrueIfNull(this.sticky); + public UpdatePolicy updatePolicy() { + return Objects.requireNonNullElse(this.updatePolicy, UpdatePolicy.HARD); } boolean observabilityIncluded() { @@ -184,10 +188,10 @@ CompilationOptions acceptTheirs(CompilationOptions theirOptions) { } else { compilationOptionsBuilder.setListConflictedClasses(this.listConflictedClasses); } - if (theirOptions.sticky != null) { - compilationOptionsBuilder.setSticky(theirOptions.sticky); + if (theirOptions.updatePolicy != null) { + compilationOptionsBuilder.setUpdatePolicy(theirOptions.updatePolicy); } else { - compilationOptionsBuilder.setSticky(this.sticky); + compilationOptionsBuilder.setUpdatePolicy(this.updatePolicy); } if (theirOptions.withCodeGenerators != null) { compilationOptionsBuilder.withCodeGenerators(theirOptions.withCodeGenerators); @@ -273,7 +277,7 @@ public static class CompilationOptionsBuilder { private Boolean dumpBirFile; private String cloud; private Boolean listConflictedClasses; - private Boolean sticky; + private UpdatePolicy updatePolicy; private Boolean dumpGraph; private Boolean dumpRawGraph; private Boolean withCodeGenerators; @@ -291,8 +295,8 @@ public CompilationOptionsBuilder setOffline(Boolean value) { return this; } - public CompilationOptionsBuilder setSticky(Boolean value) { - sticky = value; + public CompilationOptionsBuilder setUpdatePolicy(UpdatePolicy value) { + updatePolicy = value; return this; } @@ -378,7 +382,7 @@ public CompilationOptionsBuilder setOptimizeDependencyCompilation(Boolean value) public CompilationOptions build() { return new CompilationOptions(offline, observabilityIncluded, dumpBir, - dumpBirFile, cloud, listConflictedClasses, sticky, dumpGraph, dumpRawGraph, + dumpBirFile, cloud, listConflictedClasses, updatePolicy, dumpGraph, dumpRawGraph, withCodeGenerators, withCodeModifiers, configSchemaGen, exportOpenAPI, exportComponentModel, enableCache, disableSyntaxTree, remoteManagement, optimizeDependencyCompilation); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Package.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Package.java index 6b9c329744be..d9f031509c97 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Package.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/Package.java @@ -1,6 +1,7 @@ package io.ballerina.projects; import io.ballerina.projects.environment.ResolutionOptions; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.DefaultDiagnosticResult; import io.ballerina.projects.internal.DependencyManifestBuilder; import io.ballerina.projects.internal.ManifestBuilder; @@ -425,8 +426,11 @@ public Modifier modify() { public PackageResolution getResolution(ResolutionOptions resolutionOptions) { boolean offline = resolutionOptions.offline(); - boolean sticky = resolutionOptions.sticky(); - CompilationOptions newCompOptions = CompilationOptions.builder().setOffline(offline).setSticky(sticky).build(); + UpdatePolicy updatePolicy = resolutionOptions.updatePolicy(); + CompilationOptions newCompOptions = CompilationOptions.builder() + .setOffline(offline) + .setUpdatePolicy(updatePolicy) + .build(); newCompOptions = newCompOptions.acceptTheirs(project.currentPackage().compilationOptions()); return this.packageContext.getResolution(newCompOptions, true); } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java index bc4d73dd0a25..1f49ceee9fe5 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/PackageResolution.java @@ -20,7 +20,6 @@ import io.ballerina.projects.DependencyGraph.DependencyGraphBuilder; import io.ballerina.projects.environment.ModuleLoadRequest; import io.ballerina.projects.environment.PackageCache; -import io.ballerina.projects.environment.PackageLockingMode; import io.ballerina.projects.environment.PackageResolver; import io.ballerina.projects.environment.ProjectEnvironment; import io.ballerina.projects.environment.ResolutionOptions; @@ -30,6 +29,7 @@ import io.ballerina.projects.internal.DefaultDiagnosticResult; import io.ballerina.projects.internal.ImportModuleRequest; import io.ballerina.projects.internal.ImportModuleResponse; +import io.ballerina.projects.internal.PackageLockingModeResolutionOptions; import io.ballerina.projects.internal.ModuleResolver; import io.ballerina.projects.internal.PackageContainer; import io.ballerina.projects.internal.PackageDiagnostic; @@ -65,7 +65,7 @@ import static io.ballerina.projects.util.ProjectConstants.EQUAL; import static io.ballerina.projects.util.ProjectConstants.OFFLINE_FLAG; import static io.ballerina.projects.util.ProjectConstants.REPOSITORY_FLAG; -import static io.ballerina.projects.util.ProjectConstants.STICKY_FLAG; +import static io.ballerina.projects.util.ProjectConstants.UPDATE_POLICY_FLAG; /** * Resolves dependencies and handles version conflicts in the dependency graph. @@ -135,7 +135,7 @@ private void generateCaches() { List cmdArgs = new ArrayList<>(); cmdArgs.add(System.getProperty(BALLERINA_HOME) + "/bin/bal"); cmdArgs.add("pull"); - cmdArgs.add(STICKY_FLAG + EQUAL + resolutionOptions.sticky()); + cmdArgs.add(UPDATE_POLICY_FLAG + EQUAL + resolutionOptions.updatePolicy()); cmdArgs.add(OFFLINE_FLAG + EQUAL + resolutionOptions.offline()); // Specify which repository to resolve the dependency from @@ -303,7 +303,7 @@ public boolean autoUpdate() { */ private DependencyGraph buildDependencyGraph() { // TODO We should get diagnostics as well. Need to design that contract - if (rootPackageContext.project().kind() == ProjectKind.BALA_PROJECT && this.resolutionOptions.sticky()) { + if (rootPackageContext.project().kind() == ProjectKind.BALA_PROJECT) { return resolveBALADependencies(); } else { return resolveSourceDependencies(); @@ -359,17 +359,9 @@ private DependencyGraph resolveSourceDependencies() { // 1) Get PackageLoadRequests for all the direct dependencies of this package LinkedHashSet moduleLoadRequests = getModuleLoadRequestsOfDirectDependencies(); - boolean hasDependencyManifest = rootPackageContext.dependenciesTomlContext().isPresent(); - SemanticVersion prevDistributionVersion = rootPackageContext.dependencyManifest().distributionVersion(); - boolean distributionChange = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()) - .equals(prevDistributionVersion); - boolean lessThan24HrsAfterBuild = ProjectUtils.isWithin24HoursOfLastBuild(rootPackageContext.project()); - // 2) Resolve imports to packages and create the complete dependency graph with package metadata - // TODO: find a better way to pass the new parameters. ResolutionEngine resolutionEngine = new ResolutionEngine(rootPackageContext.descriptor(), - blendedManifest, packageResolver, moduleResolver, resolutionOptions, - hasDependencyManifest, distributionChange, lessThan24HrsAfterBuild); + blendedManifest, packageResolver, moduleResolver, resolutionOptions); DependencyGraph dependencyNodeGraph = resolutionEngine.resolveDependencies(moduleLoadRequests); this.dependencyGraphDump = resolutionEngine.dumpGraphs(); @@ -474,8 +466,7 @@ private void addDeprecationDiagnostic(PackageDescriptor pkgDesc) { } private ResolutionRequest createFromDepNode(DependencyNode depNode) { - return ResolutionRequest.from(depNode.pkgDesc(), depNode.scope(), depNode.resolutionType(), - resolutionOptions.packageLockingMode()); + return ResolutionRequest.from(depNode.pkgDesc(), depNode.scope(), depNode.resolutionType()); } private DependencyGraph createDependencyNodeGraph( @@ -577,62 +568,51 @@ private BlendedManifest createBlendedManifest(PackageContext rootPackageContext, private ResolutionOptions getResolutionOptions(PackageContext rootPackageContext, CompilationOptions compilationOptions) { - boolean sticky = ProjectUtils.getSticky(rootPackageContext.project()); - this.autoUpdate = !sticky; - PackageLockingMode packageLockingMode; - SemanticVersion prevDistributionVersion = rootPackageContext.dependencyManifest().distributionVersion(); - SemanticVersion currentDistributionVersion = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()); - - // For new projects, the locking mode will be SOFT unless sticky == true. - // For existing projects, if the package was built with a previous distribution, the locking mode - // will be SOFT unless sticky == true. A warning is issued to notify the detection of the new distribution. - if (rootPackageContext.dependenciesTomlContext().isPresent()) { - // existing project - if (prevDistributionVersion == null) { - // Built with Update 4 or less. Therefore, we issue a warning - addOlderSLUpdateDistributionDiagnostic(null, currentDistributionVersion); - if (!sticky) { - packageLockingMode = PackageLockingMode.SOFT; - } else { - packageLockingMode = PackageLockingMode.MEDIUM; - } - } else { - // Built with Update 5 or above - boolean newUpdateDistribution = - isNewUpdateDistribution(prevDistributionVersion, currentDistributionVersion); - if (newUpdateDistribution) { - addOlderSLUpdateDistributionDiagnostic(prevDistributionVersion, currentDistributionVersion); - packageLockingMode = PackageLockingMode.SOFT; - } else { - packageLockingMode = PackageLockingMode.MEDIUM; - } - } - } else { - // new project - if (!sticky) { - packageLockingMode = PackageLockingMode.SOFT; - } else { - packageLockingMode = PackageLockingMode.MEDIUM; - } + // add a warning if the package was built with an older distribution + SemanticVersion previousDistributionVersion = rootPackageContext.dependencyManifest().distributionVersion(); + if (isDistributionNewMinorChange(previousDistributionVersion)) { + addOlderSLUpdateDistributionDiagnostic(previousDistributionVersion); } + boolean hasDependencyManifest = rootPackageContext.dependenciesTomlContext().isPresent(); + boolean distributionChange = isDistributionMinorChange(previousDistributionVersion); + boolean lessThan24HrsAfterBuild = ProjectUtils.isWithin24HoursOfLastBuild(rootPackageContext.project()); + PackageLockingModeResolutionOptions packageLockingModeResolutionOptions = new PackageLockingModeResolutionOptions( + compilationOptions.updatePolicy(), + hasDependencyManifest, + distributionChange, + lessThan24HrsAfterBuild); + // TODO: autoUpdate should be true iff Direct & Transitive locking modes are LOCKED. But we can't decide it here yet. + // We can decide in resolution engine though. return ResolutionOptions.builder() .setOffline(compilationOptions.offlineBuild()) - .setSticky(sticky) + .setUpdatePolicy(compilationOptions.updatePolicy()) .setDumpGraph(compilationOptions.dumpGraph()) .setDumpRawGraphs(compilationOptions.dumpRawGraphs()) - .setPackageLockingMode(packageLockingMode) + .setLockingModeResolutionOptions(packageLockingModeResolutionOptions) .build(); } - private boolean isNewUpdateDistribution(SemanticVersion prevDistributionVersion, - SemanticVersion currentDistributionVersion) { - return currentDistributionVersion.major() == prevDistributionVersion.major() - && currentDistributionVersion.minor() > prevDistributionVersion.minor(); + private boolean isDistributionNewMinorChange(SemanticVersion prevDistributionVersion) { + SemanticVersion currentDistributionVersion = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()); + if (prevDistributionVersion == null) { + return true; + } + assert currentDistributionVersion.major() == prevDistributionVersion.major(); + return currentDistributionVersion.minor() > prevDistributionVersion.minor(); + } + + private boolean isDistributionMinorChange(SemanticVersion prevDistributionVersion) { + SemanticVersion currentDistributionVersion = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()); + if (prevDistributionVersion == null) { + return true; + } + assert currentDistributionVersion.major() == prevDistributionVersion.major(); + return currentDistributionVersion.minor() != prevDistributionVersion.minor(); } - private void addOlderSLUpdateDistributionDiagnostic(SemanticVersion prevDistributionVersion, - SemanticVersion currentDistributionVersion) { + private void addOlderSLUpdateDistributionDiagnostic(SemanticVersion prevDistributionVersion) { + SemanticVersion currentDistributionVersion = SemanticVersion.from(RepoUtils.getBallerinaShortVersion()); String currentVersionForDiagnostic = String.valueOf(currentDistributionVersion.minor()); if (currentDistributionVersion.patch() != 0) { currentVersionForDiagnostic += DOT + currentDistributionVersion.patch(); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java index 9f853cc736f9..54e32ef4e9ab 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/environment/ResolutionOptions.java @@ -17,6 +17,8 @@ */ package io.ballerina.projects.environment; +import io.ballerina.projects.internal.PackageLockingModeResolutionOptions; + /** * Holds the options used by the package resolution components. * @@ -24,20 +26,22 @@ */ public class ResolutionOptions { private final boolean offline; - private final boolean sticky; +// private final boolean sticky; + private final UpdatePolicy updatePolicy; private final boolean dumpGraph; private final boolean dumpRawGraphs; - private final UpdatePolicy updatePolicy; - private final PackageLockingMode packageLockingMode; +// private final PackageLockingMode packageLockingMode; + private final PackageLockingModeResolutionOptions packageLockingModeResolutionOptions; - private ResolutionOptions(boolean offline, boolean sticky, boolean dumpGraph, boolean dumpRawGraphs, - UpdatePolicy updatePolicy, PackageLockingMode packageLockingMode) { + private ResolutionOptions(boolean offline, UpdatePolicy updatePolicy, boolean dumpGraph, boolean dumpRawGraphs, + PackageLockingModeResolutionOptions packageLockingModeResolutionOptions) { this.offline = offline; - this.sticky = sticky; +// this.sticky = sticky; this.dumpGraph = dumpGraph; this.dumpRawGraphs = dumpRawGraphs; this.updatePolicy = updatePolicy; - this.packageLockingMode = packageLockingMode; +// this.packageLockingMode = packageLockingMode; + this.packageLockingModeResolutionOptions = packageLockingModeResolutionOptions; } /** @@ -53,18 +57,22 @@ public boolean offline() { return offline; } +// /** +// * If the sticky mode is enabled, the resolution system attempts use the dependency versions +// * recorded in Ballerina.toml as much as possible. +// *

+// * The default value is 'true'. +// * +// * @return true if sticky model is enabled, otherwise false +// */ +// public boolean sticky() { +// return sticky; +// } + /** - * If the sticky mode is enabled, the resolution system attempts use the dependency versions - * recorded in Ballerina.toml as much as possible. - *

- * The default value is 'true'. - * - * @return true if sticky model is enabled, otherwise false + * The update policy to be used by the resolution system. + * @return the update policy */ - public boolean sticky() { - return sticky; - } - public UpdatePolicy updatePolicy() { return updatePolicy; } @@ -77,8 +85,12 @@ public boolean dumpRawGraphs() { return dumpRawGraphs; } - public PackageLockingMode packageLockingMode() { - return packageLockingMode; +// public PackageLockingMode packageLockingMode() { +// return packageLockingMode; +// } + + public PackageLockingModeResolutionOptions lockingModeResolutionOptions() { + return packageLockingModeResolutionOptions; } public static ResolutionOptionBuilder builder() { @@ -92,21 +104,22 @@ public static ResolutionOptionBuilder builder() { */ public static class ResolutionOptionBuilder { private boolean offline = false; - private boolean sticky = true; +// private boolean sticky = true; private boolean dumpGraph = false; private boolean dumpRawGraphs = false; private UpdatePolicy updatePolicy = UpdatePolicy.SOFT; - private PackageLockingMode packageLockingMode = PackageLockingMode.MEDIUM; // TODO: Remove. this isn't a user passed value anymore. Synthesized from the update policy. + private PackageLockingModeResolutionOptions packageLockingModeResolutionOptions; +// private PackageLockingMode packageLockingMode = PackageLockingMode.MEDIUM; // TODO: Remove. this isn't a user passed value anymore. Synthesized from the update policy. public ResolutionOptionBuilder setOffline(boolean value) { offline = value; return this; } - public ResolutionOptionBuilder setSticky(boolean value) { - sticky = value; - return this; - } +// public ResolutionOptionBuilder setSticky(boolean value) { +// sticky = value; +// return this; +// } public ResolutionOptionBuilder setUpdatePolicy(UpdatePolicy value) { updatePolicy = value; @@ -123,13 +136,18 @@ public ResolutionOptionBuilder setDumpRawGraphs(boolean value) { return this; } - public ResolutionOptionBuilder setPackageLockingMode(PackageLockingMode value) { - packageLockingMode = value; +// public ResolutionOptionBuilder setPackageLockingMode(PackageLockingMode value) { +// packageLockingMode = value; +// return this; +// } + + public ResolutionOptionBuilder setLockingModeResolutionOptions(PackageLockingModeResolutionOptions value) { + packageLockingModeResolutionOptions = value; return this; } public ResolutionOptions build() { - return new ResolutionOptions(offline, sticky, dumpGraph, dumpRawGraphs, updatePolicy, packageLockingMode); + return new ResolutionOptions(offline, updatePolicy, dumpGraph, dumpRawGraphs, packageLockingModeResolutionOptions); } } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java deleted file mode 100644 index 6373d1c1b60f..000000000000 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolver.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; - -import io.ballerina.projects.environment.PackageLockingMode; - -/** - * This class resolves the locking modes for the packages based on the update policy and the package status. - * - * @since 2201.12.0 - */ -public class LockingModeResolver { - private final LockingModeResolutionOptions options; - - public LockingModeResolver(LockingModeResolutionOptions options) { - this.options = options; - } - - /** - * Resolves the locking modes for the package based on the update policy and the package status. - * - * @return the resolved locking modes - */ - public LockingModes resolveLockingModes() { - if (!options.hasDependencyManifest()) { - return resolveNoManifestLockingMode(); - } - if (options.distributionChange()) { - return resolveDistributionChangeLockingMode(); - } - if (options.importAddition()) { - return resolveImportAdditionLockingMode(); - } - if (options.lessThan24HrsAfterBuild()) { - return new LockingModes(PackageLockingMode.LOCKED); - } - return resolveDefaultLockingMode(); - } - - public record LockingModes( - PackageLockingMode existingDirectDepMode, - PackageLockingMode existingTransitiveDepMode, - PackageLockingMode newDirectDepMode, - PackageLockingMode newTransitiveDepMode) { - public LockingModes(PackageLockingMode existingDirectDepMode, PackageLockingMode existingTransDepMode) { - this(existingDirectDepMode, existingTransDepMode, existingDirectDepMode, existingTransDepMode); - } - public LockingModes(PackageLockingMode mode) { - this(mode, mode, mode, mode); - } - } - - private LockingModes resolveNoManifestLockingMode() { - return switch (options.updatePolicy()) { - case SOFT -> new LockingModes(PackageLockingMode.LATEST, PackageLockingMode.SOFT); - case MEDIUM -> new LockingModes(PackageLockingMode.LATEST, PackageLockingMode.MEDIUM); - case HARD -> new LockingModes(PackageLockingMode.LATEST, PackageLockingMode.HARD); - case LOCKED -> new LockingModes(PackageLockingMode.INVALID); - }; - } - - private LockingModes resolveDistributionChangeLockingMode() { - return switch (options.updatePolicy()) { - case SOFT -> new LockingModes(PackageLockingMode.SOFT); - case MEDIUM, HARD -> new LockingModes(PackageLockingMode.MEDIUM); - case LOCKED -> new LockingModes(PackageLockingMode.LOCKED); - }; - } - - private LockingModes resolveImportAdditionLockingMode() { - return switch (options.updatePolicy()) { - case SOFT -> new LockingModes(PackageLockingMode.SOFT, PackageLockingMode.SOFT, - PackageLockingMode.LATEST, PackageLockingMode.SOFT); - case MEDIUM -> new LockingModes(PackageLockingMode.MEDIUM, PackageLockingMode.MEDIUM, - PackageLockingMode.LATEST, PackageLockingMode.MEDIUM); - case HARD -> new LockingModes(PackageLockingMode.HARD, PackageLockingMode.HARD, - PackageLockingMode.LATEST, PackageLockingMode.HARD); - case LOCKED -> new LockingModes(PackageLockingMode.INVALID); - }; - } - - private LockingModes resolveDefaultLockingMode() { - return switch (options.updatePolicy()) { - case SOFT -> new LockingModes(PackageLockingMode.SOFT); - case MEDIUM -> new LockingModes(PackageLockingMode.MEDIUM); - case HARD -> new LockingModes(PackageLockingMode.HARD); - case LOCKED -> new LockingModes(PackageLockingMode.LOCKED); - }; - } -} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ManifestBuilder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ManifestBuilder.java index 39399a550de6..cc3d526b7072 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ManifestBuilder.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ManifestBuilder.java @@ -30,6 +30,7 @@ import io.ballerina.projects.ProjectException; import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.TomlDocument; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.model.BalToolDescriptor; import io.ballerina.projects.internal.model.CompilerPluginDescriptor; import io.ballerina.projects.util.FileUtils; @@ -87,7 +88,7 @@ * * @since 2.0.0 */ -public class ManifestBuilder { +public class ManifestBuilder { //TODO: add the update policy here and to the Dependencies.toml private final TomlDocument ballerinaToml; private final TomlDocument compilerPluginToml; @@ -341,7 +342,7 @@ private List getModuleEntries( String modReadme = ""; if (Files.exists(modReadmePath)) { modReadme = Paths.get(ProjectConstants.MODULES_ROOT).resolve(pathEntry.getKey()) - .resolve(ProjectConstants.README_MD_FILE_NAME).toString();; + .resolve(ProjectConstants.README_MD_FILE_NAME).toString(); } PackageManifest.Module module = new PackageManifest.Module( packageName + DOT + Optional.of(pathEntry.getValue().getFileName()).get(), false, @@ -892,8 +893,14 @@ private BuildOptions setBuildOptions(TomlTableNode tomlTableNode) { final TopLevelNode topLevelNode = tableNode.entries().get(CompilerOptionName.CLOUD.toString()); Boolean dumpBuildTime = getBooleanFromBuildOptionsTableNode(tableNode, BuildOptions.OptionName.DUMP_BUILD_TIME.toString()); - Boolean sticky = - getTrueFromBuildOptionsTableNode(tableNode, CompilerOptionName.STICKY.toString()); + UpdatePolicy updatePolicy = UpdatePolicy.HARD; + try { + String updatePolicyStr = getStringFromBuildOptionsTableNode(tableNode, + CompilerOptionName.UPDATE_POLICY.toString()); + if (updatePolicyStr != null) { + updatePolicy = UpdatePolicy.valueOf(updatePolicyStr); + } + } catch (IllegalArgumentException ignore) {} String cloud = ""; if (topLevelNode != null) { cloud = getStringFromTomlTableNode(topLevelNode); @@ -925,7 +932,7 @@ private BuildOptions setBuildOptions(TomlTableNode tomlTableNode) { .setCloud(cloud) .setListConflictedClasses(listConflictedClasses) .setDumpBuildTime(dumpBuildTime) - .setSticky(sticky) + .setUpdatePolicy(updatePolicy) .setEnableCache(enableCache) .setNativeImage(nativeImage) .setExportComponentModel(exportComponentModel) diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeMatrix.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeMatrix.java new file mode 100644 index 000000000000..6a25970cb688 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeMatrix.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; + +import io.ballerina.projects.environment.PackageLockingMode; + +/** + * Represents the locking modes for existing/new, direct/transitive dependencies. + * + * @since 2201.12.0 + * + * @param existingDirectDepMode locking mode for existing direct dependencies + * @param existingTransitiveDepMode locking mode for existing transitive dependencies + * @param newDirectDepMode locking mode for new direct dependencies + * @param newTransitiveDepMode locking mode for new transitive dependencies + */ +public record PackageLockingModeMatrix( + PackageLockingMode existingDirectDepMode, + PackageLockingMode existingTransitiveDepMode, + PackageLockingMode newDirectDepMode, + PackageLockingMode newTransitiveDepMode) { + public PackageLockingModeMatrix(PackageLockingMode existingDirectDepMode, PackageLockingMode existingTransDepMode) { + this(existingDirectDepMode, existingTransDepMode, existingDirectDepMode, existingTransDepMode); + } + + public PackageLockingModeMatrix(PackageLockingMode mode) { + this(mode, mode, mode, mode); + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolutionOptions.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeResolutionOptions.java similarity index 79% rename from compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolutionOptions.java rename to compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeResolutionOptions.java index 06626f11642a..59e88681dfef 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/LockingModeResolutionOptions.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeResolutionOptions.java @@ -25,9 +25,8 @@ * * @since 2201.12.0 */ -public record LockingModeResolutionOptions(UpdatePolicy updatePolicy, boolean hasDependencyManifest, - boolean distributionChange, boolean importAddition, - boolean lessThan24HrsAfterBuild) { +public record PackageLockingModeResolutionOptions(UpdatePolicy updatePolicy, boolean hasDependencyManifest, + boolean distributionChange, boolean lessThan24HrsAfterBuild) { /** * The update policy of the package as passed by the user. @@ -59,16 +58,6 @@ public boolean distributionChange() { return distributionChange; } - /** - * Whether the package has new imports added. - * - * @return true if the package has new imports added, false otherwise - */ - @Override - public boolean importAddition() { - return importAddition; - } - /** * Whether the package is built less than 24 hours after the previous build. * diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeResolver.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeResolver.java new file mode 100644 index 000000000000..721e7c593601 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/PackageLockingModeResolver.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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.ballerina.projects.internal; + +import io.ballerina.projects.environment.PackageLockingMode; + +/** + * This class resolves the locking modes for the packages based on the update policy and the package status. + * + * @since 2201.12.0 + */ +public class PackageLockingModeResolver { + private final PackageLockingModeResolutionOptions options; + + public PackageLockingModeResolver(PackageLockingModeResolutionOptions options) { + this.options = options; + } + + /** + * Resolves the locking modes for the package based on the update policy and the package status. + * + * @return the resolved locking modes + */ + public PackageLockingModeMatrix resolveLockingModes(boolean importAddition) { + if (!options.hasDependencyManifest()) { + return resolveNoManifestLockingMode(); + } + if (options.distributionChange()) { + return resolveDistributionChangeLockingMode(); + } + if (importAddition) { + return resolveImportAdditionLockingMode(); + } + if (options.lessThan24HrsAfterBuild()) { + return new PackageLockingModeMatrix(PackageLockingMode.LOCKED); + } + return resolveDefaultLockingMode(); + } + + private PackageLockingModeMatrix resolveNoManifestLockingMode() { + return switch (options.updatePolicy()) { + case SOFT -> new PackageLockingModeMatrix(PackageLockingMode.LATEST, PackageLockingMode.SOFT); + case MEDIUM -> new PackageLockingModeMatrix(PackageLockingMode.LATEST, PackageLockingMode.MEDIUM); + case HARD -> new PackageLockingModeMatrix(PackageLockingMode.LATEST, PackageLockingMode.HARD); + case LOCKED -> new PackageLockingModeMatrix(PackageLockingMode.INVALID); + }; + } + + private PackageLockingModeMatrix resolveDistributionChangeLockingMode() { + return switch (options.updatePolicy()) { + case SOFT -> new PackageLockingModeMatrix(PackageLockingMode.SOFT); + case MEDIUM, HARD -> new PackageLockingModeMatrix(PackageLockingMode.MEDIUM); + case LOCKED -> new PackageLockingModeMatrix(PackageLockingMode.LOCKED); + }; + } + + private PackageLockingModeMatrix resolveImportAdditionLockingMode() { + return switch (options.updatePolicy()) { + case SOFT -> new PackageLockingModeMatrix(PackageLockingMode.SOFT, PackageLockingMode.SOFT, + PackageLockingMode.LATEST, PackageLockingMode.SOFT); + case MEDIUM -> new PackageLockingModeMatrix(PackageLockingMode.MEDIUM, PackageLockingMode.MEDIUM, + PackageLockingMode.LATEST, PackageLockingMode.MEDIUM); + case HARD -> new PackageLockingModeMatrix(PackageLockingMode.HARD, PackageLockingMode.HARD, + PackageLockingMode.LATEST, PackageLockingMode.HARD); + case LOCKED -> new PackageLockingModeMatrix(PackageLockingMode.INVALID); + }; + } + + private PackageLockingModeMatrix resolveDefaultLockingMode() { + return switch (options.updatePolicy()) { + case SOFT -> new PackageLockingModeMatrix(PackageLockingMode.SOFT); + case MEDIUM -> new PackageLockingModeMatrix(PackageLockingMode.MEDIUM); + case HARD -> new PackageLockingModeMatrix(PackageLockingMode.HARD); + case LOCKED -> new PackageLockingModeMatrix(PackageLockingMode.LOCKED); + }; + } +} diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java index a77137c9d3f8..fceb5bc3513c 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/ResolutionEngine.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -59,19 +60,14 @@ public class ResolutionEngine { private final List diagnostics; private String dependencyGraphDump; private DiagnosticResult diagnosticResult; - private Set unresolvedDeps = null; - boolean hasDependencyManifest; - boolean distributionChange; - boolean lessThan24HrsAfterBuild; + private final Set unresolvedDeps; + private Collection directDependencies; public ResolutionEngine(PackageDescriptor rootPkgDesc, BlendedManifest blendedManifest, PackageResolver packageResolver, ModuleResolver moduleResolver, - ResolutionOptions resolutionOptions, - boolean hasDependencyManifest, - boolean distributionChange, - boolean lessThan24HrsAfterBuild) { + ResolutionOptions resolutionOptions) { this.rootPkgDesc = rootPkgDesc; this.blendedManifest = blendedManifest; this.packageResolver = packageResolver; @@ -80,9 +76,8 @@ public ResolutionEngine(PackageDescriptor rootPkgDesc, this.graphBuilder = new PackageDependencyGraphBuilder(rootPkgDesc); this.diagnostics = new ArrayList<>(); this.dependencyGraphDump = ""; - this.hasDependencyManifest = hasDependencyManifest; - this.distributionChange = distributionChange; // TODO move these into a single object - this.lessThan24HrsAfterBuild = lessThan24HrsAfterBuild; // TODO move these into a single object + this.unresolvedDeps = new HashSet<>(); + this.directDependencies = new ArrayList<>(); } public DiagnosticResult diagnosticResult() { @@ -94,32 +89,26 @@ public DiagnosticResult diagnosticResult() { public DependencyGraph resolveDependencies(Collection moduleLoadRequests) { // 1) Resolve import declarations into Packages. - Collection directDependencies = resolvePackages(moduleLoadRequests); + directDependencies = resolvePackages(moduleLoadRequests); - LockingModeResolver.LockingModes lockingModes = getLockingModes(directDependencies); - PackageDependencyGraphBuilder graphBuilder = new PackageDependencyGraphBuilder(rootPkgDesc); + PackageLockingModeMatrix lockingModes = getLockingModes(directDependencies); Queue unresolvedNodes = new LinkedList<>(); - initializeDirectDependencies(directDependencies, lockingModes, graphBuilder, unresolvedNodes); - processUnresolvedNodes(lockingModes, graphBuilder, unresolvedNodes); + initializeDirectDependencies(directDependencies, lockingModes, unresolvedNodes); + processUnresolvedNodes(lockingModes, unresolvedNodes); return graphBuilder.buildGraph(); +// return buildFinalDependencyGraph(); } - private LockingModeResolver.LockingModes getLockingModes(Collection directDependencies) { + private PackageLockingModeMatrix getLockingModes(Collection directDependencies) { boolean importAddition = areNewDirectDependenciesAdded(directDependencies); - LockingModeResolutionOptions options = new LockingModeResolutionOptions( - resolutionOptions.updatePolicy(), - hasDependencyManifest, - distributionChange, - importAddition, - lessThan24HrsAfterBuild); - LockingModeResolver lockingModeResolver = new LockingModeResolver(options); - return lockingModeResolver.resolveLockingModes(); + PackageLockingModeResolver packageLockingModeResolver = new PackageLockingModeResolver( + resolutionOptions.lockingModeResolutionOptions()); + return packageLockingModeResolver.resolveLockingModes(importAddition); } private void initializeDirectDependencies( Collection directDependencies, - LockingModeResolver.LockingModes lockingModeMap, - PackageDependencyGraphBuilder graphBuilder, + PackageLockingModeMatrix lockingModeMap, Queue unresolvedNodes) { for (DependencyNode directDependency : directDependencies) { PackageLockingMode directDepLockingMode = isDirectDependencyNewlyAdded(directDependency) ? @@ -130,15 +119,15 @@ private void initializeDirectDependencies( } private void processUnresolvedNodes( - LockingModeResolver.LockingModes lockingModeMap, - PackageDependencyGraphBuilder graphBuilder, + PackageLockingModeMatrix lockingModeMap, Queue unresolvedNodes) { while (!unresolvedNodes.isEmpty()) { + // TODO: improvement: built-in packages go through the queue quite unnecessarily. + // Make a cache of them when gone through the queue once and use it. UnresolvedNode unresolvedNode = unresolvedNodes.remove(); DependencyNode pkgNode = unresolvedNode.dependencyNode(); PackageDescriptor packageToResolve = pkgNode.pkgDesc(); - // TODO: make locking mode, a part of the node itself. PackageLockingMode packageLockingMode = unresolvedNode.packageLockingMode(); PackageVersion nodeVersion = packageToResolve.version(); BlendedManifest.Dependency manifestPackage = blendedManifest.dependency( @@ -168,8 +157,9 @@ private void processUnresolvedNodes( PackageMetadataResponse response = packageResolver.resolvePackageMetadata( resolutionRequest, resolutionOptions); if (response.resolutionStatus() == ResolutionResponse.ResolutionStatus.UNRESOLVED) { - // TODO error - throw new ProjectException("Failed to resolve package: " + packageToResolve); + // TODO add diagnostic + unresolvedDeps.add(pkgNode); + continue; } DependencyNode updatedPkgNode = new DependencyNode( response.resolvedDescriptor(), @@ -180,8 +170,9 @@ private void processUnresolvedNodes( // If there is a higher version of the dependency is already in the graph, we use that. PackageVersion depVersion = dep.version(); PackageDescriptor currentIndexDependency = graphBuilder.getDependency(dep.org(), dep.name()); - if (currentIndexDependency != null && - currentIndexDependency.version().value().greaterThanOrEqualTo(dep.version().value())) { + if (currentIndexDependency != null + && currentIndexDependency.version() != null + && currentIndexDependency.version().value().greaterThanOrEqualTo(dep.version().value())) { depVersion = currentIndexDependency.version(); } PackageDescriptor depDesc = PackageDescriptor.from(dep.org(), dep.name(), depVersion); @@ -197,6 +188,13 @@ private void processUnresolvedNodes( } } + private DependencyGraph buildFinalDependencyGraph() { + DependencyGraph dependencyGraph = graphBuilder.buildGraph(); +// this.diagnostics.addAll(graphBuilder.diagnostics()); // TODO: add diagnostics to the graph + dumpDependencyGraph(dependencyGraph); + return dependencyGraph; + } + private Optional getCurrentlyBestVersion( PackageVersion queuedVersion, PackageVersion manifestVersion, @@ -271,6 +269,7 @@ private boolean isDirectDependencyNewlyAdded(DependencyNode directDependency) { if (isNewDependency(directDependency)) { return true; } + // If the dependency is not new, check if it was a transitive dependency and now a direct dependency. BlendedManifest.Dependency manifestDep = blendedManifest.dependency( directDependency.pkgDesc().org(), directDependency.pkgDesc().name()).orElseThrow(); return manifestDep.relation().equals(BlendedManifest.DependencyRelation.TRANSITIVE) || @@ -335,6 +334,26 @@ private Collection resolvePackages(Collection return directDeps; } + private void dumpDependencyGraph(DependencyGraph dependencyGraph) { + if (!resolutionOptions.dumpGraph() && !resolutionOptions.dumpRawGraphs()) { + return; + } + List unresolvedDirectDeps = new ArrayList<>(); + for (DependencyNode directDep : directDependencies) { + if (unresolvedDeps.contains(directDep)) { + unresolvedDirectDeps.add(directDep); + } + } + Collection resolvedDirectDeps = + dependencyGraph.getDirectDependencies(dependencyGraph.getRoot()); + unresolvedDirectDeps.removeAll(resolvedDirectDeps); // TODO: incorrect + + String serializedGraph = DotGraphs.serializeDependencyNodeGraph( + dependencyGraph, this.unresolvedDeps, unresolvedDirectDeps); + dependencyGraphDump += "\n"; + dependencyGraphDump += (serializedGraph + "\n"); + } + public String dumpGraphs() { return dependencyGraphDump; } @@ -418,7 +437,7 @@ public int hashCode() { public String toString() { String attr = " [scope=" + scope + ",kind=" + resolutionType + ",repo=" + pkgDesc.repository().orElse(null) + ",error=" + isError + "]"; - return pkgDesc.toString() + attr; + return pkgDesc + attr; } @Override diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/environment/DefaultPackageResolver.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/environment/DefaultPackageResolver.java index c93183941c48..9d59be441257 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/environment/DefaultPackageResolver.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/environment/DefaultPackageResolver.java @@ -104,8 +104,7 @@ public Collection resolvePackageNames(Collection packageVersions = distributionRepo.getPackageVersions(resolutionRequest, options); // If module exists in both repos, then we check if a newer version of diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java index b75277587fde..8352523d7266 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/IndexPackage.java @@ -82,6 +82,10 @@ static IndexPackage from( supportedPlatform, ballerinaVersion, dependencies, modules, isDeprecated, deprecationMsg); } + public PackageDescriptor packageDescriptor() { + return packageDescriptor; + } + public PackageName name() { return packageDescriptor.name(); } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexUpdater.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexUpdater.java index 4efbd56a2df7..a4e2e93e2118 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexUpdater.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/index/PackageIndexUpdater.java @@ -26,12 +26,15 @@ import org.eclipse.jgit.api.CreateBranchCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.transport.CredentialsProvider; import org.wso2.ballerinalang.util.RepoUtils; import java.io.File; import java.io.IOException; import java.io.PrintStream; +import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -39,6 +42,7 @@ import java.util.List; import java.util.stream.Stream; +import static io.ballerina.projects.util.ProjectConstants.INDEX_JSON_FILE_NAME; import static org.wso2.ballerinalang.util.RepoUtils.SET_BALLERINA_DEV_CENTRAL; /** @@ -111,8 +115,8 @@ public void loadOrg(PackageOrg packageOrg) { public void loadPackage(PackageOrg packageOrg, PackageName packageName) { updateIndexRepoCache(); Path orgDir = indexDirectory.resolve(indexGitRepoName).resolve(packageOrg.value()); - Path packageFile = orgDir.resolve(packageName.value() + ".json"); - if (!Files.exists(packageFile)) { + Path packageFile = orgDir.resolve(packageName.value()).resolve(INDEX_JSON_FILE_NAME); + if (!Files.isRegularFile(packageFile)) { return; } try { @@ -126,7 +130,7 @@ public void loadPackage(PackageOrg packageOrg, PackageName packageName) { } packageIndex.addPackage(packagesOfOrg); } catch (IOException e) { - throw new ProjectException("Error reading index file: " + e.getMessage(), e); + throw new ProjectException("Error reading index file: " + packageFile + ": " + e.getMessage(), e); } } @@ -179,7 +183,10 @@ private void checkoutToBranch() { private void fetchIndexHead() { // TODO: add a progress bar try (Git git = Git.open(indexDirectory.resolve(indexGitRepoName).toFile())) { - git.pull().setCredentialsProvider(credentialsProvider).call(); + git.pull() + .setContentMergeStrategy(ContentMergeStrategy.THEIRS) + .setProgressMonitor(new TextProgressMonitor(new PrintWriter(System.out))) // TODO: customize + .setCredentialsProvider(credentialsProvider).call(); } catch (IOException | GitAPIException e) { throw new ProjectException("Error while pulling the latest changes from the upstream: " + e.getMessage(), e); diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/RemotePackageRepository.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/RemotePackageRepository.java index 074a2fdbf265..24529b1aaaeb 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/RemotePackageRepository.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/repositories/RemotePackageRepository.java @@ -386,7 +386,7 @@ public PackageMetadataResponse getPackageMetadata(ResolutionRequest request, // If not resolved, might be a private package. Let's try to resolve with the central API. PackageResolutionRequest packageResolutionRequest = toPackageResolutionRequest(request); try { - return fromPackageResolutionResponse(request, packageResolutionRequest); + return fromPackageResolutionResponse(request, packageResolutionRequest); // TODO: check why the error } catch (CentralClientException e) { throw new ProjectException(e.getMessage()); } @@ -396,10 +396,11 @@ private PackageMetadataResponse createResolutionResponse(ResolutionRequest reque if (resolvedPackage == null) { return PackageMetadataResponse.createUnresolvedResponse(request); } - if (ProjectUtils.isDistributionSupported(resolvedPackage.ballerinaVersion())) { + if (!ProjectUtils.isDistributionSupported(resolvedPackage.ballerinaVersion())) { return PackageMetadataResponse.createUnresolvedResponse(request); } - return PackageMetadataResponse.from(request, request.packageDescriptor(), resolvedPackage.dependencies()); + return PackageMetadataResponse.from(request, resolvedPackage.packageDescriptor(), + resolvedPackage.dependencies()); } private List filterDistributionCompatiblePackages(List indexPackages) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/DependencyUtils.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/DependencyUtils.java index 5bd9e06496fe..48029c99e661 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/DependencyUtils.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/DependencyUtils.java @@ -19,6 +19,7 @@ import io.ballerina.projects.CompilationOptions; import io.ballerina.projects.Project; +import io.ballerina.projects.environment.UpdatePolicy; /** * Project dependencies related util methods. @@ -38,7 +39,7 @@ private DependencyUtils() { */ public static void pullMissingDependencies(Project project) { CompilationOptions.CompilationOptionsBuilder compilationOptionsBuilder = CompilationOptions.builder(); - compilationOptionsBuilder.setOffline(false).setSticky(false); + compilationOptionsBuilder.setOffline(false).setUpdatePolicy(UpdatePolicy.SOFT); project.currentPackage().getResolution(compilationOptionsBuilder.build()); } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java index 2010343eec12..5d3143a2b72e 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectConstants.java @@ -114,6 +114,7 @@ private ProjectConstants() {} public static final String REPO_CACHE_DIR_NAME = "cache"; public static final String REPO_BIR_CACHE_NAME = "bir"; public static final String INDEX_DIR_NAME = "index"; + public static final String INDEX_JSON_FILE_NAME = "index.json"; // Test framework related constants public static final String TEST_RUNTIME_JAR_PREFIX = "testerina-runtime-"; @@ -150,7 +151,7 @@ private ProjectConstants() {} public static final String ORG = "org"; public static final String PACKAGE_NAME = "name"; public static final String LOCAL_TOOLS_JSON = "local-tools.json"; - public static final String STICKY_FLAG = "--sticky"; + public static final String UPDATE_POLICY_FLAG = "--update-policy"; public static final String OFFLINE_FLAG = "--offline"; public static final String REPOSITORY_FLAG = "--repository"; public static final String WILD_CARD = "*"; diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java index 28e60835b2ac..c16c8453651e 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/util/ProjectUtils.java @@ -1300,37 +1300,11 @@ public static Path getPackagePath(Path balaDirPath, String org, String name, Str } /** - * Get the sticky status of a project. + * Checks if the last build of the project is within 24 hours. * * @param project project instance - * @return true if the project is sticky, false otherwise + * @return true if the last build is within 24 hours, false otherwise */ - public static boolean getSticky(Project project) { - boolean sticky = project.buildOptions().sticky(); - if (sticky) { - return true; - } - - // set sticky only if `build` file exists and `last_update_time` not passed 24 hours - if (project.kind() == ProjectKind.BUILD_PROJECT) { - Path buildFilePath = project.targetDir().resolve(BUILD_FILE); - if (Files.exists(buildFilePath) && buildFilePath.toFile().length() > 0) { - try { - BuildJson buildJson = readBuildJson(buildFilePath); - // if distribution is not same, we anyway return sticky as false - if (buildJson != null && buildJson.distributionVersion() != null && - buildJson.distributionVersion().equals(RepoUtils.getBallerinaShortVersion()) && - !buildJson.isExpiredLastUpdateTime()) { - return true; - } - } catch (IOException | JsonSyntaxException e) { - // ignore - } - } - } - return false; - } - public static boolean isWithin24HoursOfLastBuild(Project project) { if (project.kind() == ProjectKind.BUILD_PROJECT) { Path buildFilePath = project.targetDir().resolve(BUILD_FILE); diff --git a/compiler/ballerina-lang/src/main/java/org/ballerinalang/compiler/CompilerOptionName.java b/compiler/ballerina-lang/src/main/java/org/ballerinalang/compiler/CompilerOptionName.java index aeda554046c0..7c787b901959 100644 --- a/compiler/ballerina-lang/src/main/java/org/ballerinalang/compiler/CompilerOptionName.java +++ b/compiler/ballerina-lang/src/main/java/org/ballerinalang/compiler/CompilerOptionName.java @@ -60,7 +60,7 @@ public enum CompilerOptionName { LIST_CONFLICTED_CLASSES("listConflictedClasses"), - STICKY("sticky"), + UPDATE_POLICY("updatePolicy"), ENABLE_CACHE("enableCache"), REMOTE_MANAGEMENT("remoteManagement"), diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/RemotePackageRepositoryTests.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/RemotePackageRepositoryTests.java index cc2787f34fa2..009bf05e93e6 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/RemotePackageRepositoryTests.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/RemotePackageRepositoryTests.java @@ -7,6 +7,7 @@ import io.ballerina.projects.environment.ResolutionResponse; import io.ballerina.projects.internal.ImportModuleRequest; import io.ballerina.projects.internal.ImportModuleResponse; +import io.ballerina.projects.internal.index.PackageIndex; import io.ballerina.projects.internal.repositories.FileSystemRepository; import io.ballerina.projects.internal.repositories.RemotePackageRepository; import org.ballerinalang.central.client.CentralAPIClient; @@ -74,14 +75,14 @@ public class RemotePackageRepositoryTests { Package smtp = new Package("ballerinax", "smtp", "1.0.0", Arrays.asList()); // File system responses PackageMetadataResponse fileHttp120 = PackageMetadataResponse - .from(resHttp120, http121, DependencyGraph.emptyGraph()); + .from(resHttp120, http121, Collections.emptyList()); PackageMetadataResponse fileCovid159 = PackageMetadataResponse - .from(resCovid156, covid159, DependencyGraph.emptyGraph()); + .from(resCovid156, covid159, Collections.emptyList()); PackageMetadataResponse fileSmtp130 = PackageMetadataResponse.createUnresolvedResponse(resSmtp130); @BeforeSuite public void setup() { - remotePackageRepository = new RemotePackageRepository(fileSystemRepository, centralAPIClient); + remotePackageRepository = new RemotePackageRepository(fileSystemRepository, centralAPIClient, PackageIndex.EMPTY_PACKAGE_INDEX); // TODO: pass a proper index } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/DefaultPackageRepository.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/DefaultPackageRepository.java index 2eaaefc03c08..8da5d27af74b 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/DefaultPackageRepository.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/DefaultPackageRepository.java @@ -70,12 +70,13 @@ protected List getPackageVersions(PackageOrg org, } @Override - protected DependencyGraph getDependencyGraph(PackageOrg org, - PackageName name, - PackageVersion version) { + protected Collection getDirectDependencies(PackageOrg org, PackageName name, PackageVersion version) { return pkgContainer.get(org, name, version) .map(PackageDescWrapper::pkgDesc) - .map(graphMap::get) + .map(pkgDesc -> { + DependencyGraph graph = graphMap.get(pkgDesc); + return graph.getDirectDependencies(pkgDesc); + }) .orElseThrow(() -> new IllegalStateException("Package cannot be found in dot graph files " + "org: " + org + ", name: " + name + ", version: " + version)); } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java index 680a6f6a0518..1be021d8ab23 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCase.java @@ -24,10 +24,11 @@ import io.ballerina.projects.environment.ResolutionOptions; import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.internal.BlendedManifest; +import io.ballerina.projects.internal.PackageLockingModeResolutionOptions; import io.ballerina.projects.internal.ModuleResolver; import io.ballerina.projects.internal.ResolutionEngine; import io.ballerina.projects.internal.ResolutionEngine.DependencyNode; -import io.ballerina.projects.internal.index.Index; +import io.ballerina.projects.internal.index.PackageIndex; import java.util.Collection; @@ -42,7 +43,7 @@ public class PackageResolutionTestCase { private final BlendedManifest blendedManifest; private final PackageResolver packageResolver; private final ModuleResolver moduleResolver; - private final Index index; + private final PackageIndex packageIndex; private final boolean hasDependencyManifest; private final boolean distributionChange; private final boolean lessThan24HrsAfterBuild; @@ -56,7 +57,7 @@ public PackageResolutionTestCase(PackageDescriptor rootPkgDesc, BlendedManifest blendedManifest, PackageResolver packageResolver, ModuleResolver moduleResolver, - Index index, + PackageIndex packageIndex, boolean hasDependencyManifest, boolean distributionChange, boolean lessThan24HrsAfterBuild, @@ -69,7 +70,7 @@ public PackageResolutionTestCase(PackageDescriptor rootPkgDesc, this.blendedManifest = blendedManifest; this.packageResolver = packageResolver; this.moduleResolver = moduleResolver; - this.index = index; + this.packageIndex = packageIndex; this.hasDependencyManifest = hasDependencyManifest; this.distributionChange = distributionChange; this.lessThan24HrsAfterBuild = lessThan24HrsAfterBuild; @@ -81,10 +82,16 @@ public PackageResolutionTestCase(PackageDescriptor rootPkgDesc, } public DependencyGraph execute(UpdatePolicy policy) { - ResolutionOptions options = ResolutionOptions.builder().setOffline(true).setUpdatePolicy(policy).build(); + PackageLockingModeResolutionOptions packageLockingModeResolutionOptions + = new PackageLockingModeResolutionOptions(policy, + hasDependencyManifest, distributionChange, lessThan24HrsAfterBuild); + ResolutionOptions options = ResolutionOptions.builder() + .setOffline(true) + .setUpdatePolicy(policy) + .setLockingModeResolutionOptions(packageLockingModeResolutionOptions) + .build(); ResolutionEngine resolutionEngine = new ResolutionEngine(rootPkgDesc, blendedManifest, - packageResolver, moduleResolver, options, index, hasDependencyManifest, distributionChange, - lessThan24HrsAfterBuild, true); + packageResolver, moduleResolver, options); return resolutionEngine.resolveDependencies(moduleLoadRequests); } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java index 738c85a0c54a..d51d4819ca2d 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java @@ -31,9 +31,7 @@ import io.ballerina.projects.PackageName; import io.ballerina.projects.PackageOrg; import io.ballerina.projects.PackageVersion; -import io.ballerina.projects.SemanticVersion; import io.ballerina.projects.environment.ModuleLoadRequest; -import io.ballerina.projects.internal.index.IndexDependency; import io.ballerina.projects.internal.index.IndexPackage; import java.util.ArrayList; @@ -95,26 +93,35 @@ public static IndexPackage getIndexPkgFromNode(Label name, Collection nodes) { String[] split = name.toString().split("/"); String[] split1 = split[1].split(":"); - String balVersionStr = "2201.0.0"; + String ballerinaVersion = "2201.0.0"; if (attrs.get("ballerinaVersion") != null) { - balVersionStr = Objects.requireNonNull(attrs.get("ballerinaVersion")).toString(); + ballerinaVersion = Objects.requireNonNull(attrs.get("ballerinaVersion")).toString(); } - SemanticVersion ballerinaVersion = SemanticVersion.from(balVersionStr); - List dependencies = new ArrayList<>(); - + List dependencies = new ArrayList<>(); + List modules = new ArrayList<>(); + modules.add(new IndexPackage.Module(split1[0])); // add the default module for (MutableNode node: nodes) { + if (node.attrs().get("other_modules") != null) { + String[] otherModuleNames = Objects.requireNonNull(node.attrs().get("other_modules")).toString().split(","); + for (String otherModuleName : otherModuleNames) { + modules.add(new IndexPackage.Module(otherModuleName)); + } + } for (Link link : node.links()) { String dependency = link.to().name().toString(); - dependencies.add(IndexDependency.from(getPkgDescFromNode(dependency))); + dependencies.add(getPkgDescFromNode(dependency)); } } return IndexPackage.from( PackageOrg.from(split[0]), PackageName.from(split1[0]), PackageVersion.from(split1[1]), - repository, + "any", ballerinaVersion, - dependencies); + dependencies, + modules, + false, + ""); } public static PackageDescriptor getPkgDescFromNode(String name, String repo) { diff --git a/distribution/zip/jballerina-tools/resources/command-completion/command-completion.csv b/distribution/zip/jballerina-tools/resources/command-completion/command-completion.csv index 089f6068ee6c..f34277cfc51d 100644 --- a/distribution/zip/jballerina-tools/resources/command-completion/command-completion.csv +++ b/distribution/zip/jballerina-tools/resources/command-completion/command-completion.csv @@ -2,13 +2,13 @@ cmdname,flags add, -t --template bal, -v -h --help --version add bindgen build pack clean dist doc encrypt format grpc help init new openapi pull push run search shell test update version bindgen, -cp -m -mvn -o --classpath --maven --modules --output --public -build, -o --cloud --debug --list-conflicted-classes --observability-included --offline --output --sticky --dump-build-time --target-dir --enable-cache -pack, -o --debug --list-conflicted-classes --observability-included --offline --output --sticky --dump-build-time --target-dir +build, -o --cloud --debug --list-conflicted-classes --observability-included --offline --output --update-policy --dump-build-time --target-dir --enable-cache +pack, -o --debug --list-conflicted-classes --observability-included --offline --output --update-policy --dump-build-time --target-dir dist, list pull remove update use doc, -c -e -o --combine --exclude --output --target-dir encrypt, format, -d --dry-run -graph, --dump-raw-graphs --offline --sticky +graph, --dump-raw-graphs --offline --update-policy grpc, --input --mode --output init, -t --template new, -t --template diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java index b99ddfc9060a..8b045d657377 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java @@ -44,6 +44,7 @@ import io.ballerina.projects.directory.BuildProject; import io.ballerina.projects.directory.ProjectLoader; import io.ballerina.projects.directory.SingleFileProject; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.util.ProjectConstants; import io.ballerina.projects.util.ProjectPaths; import io.ballerina.tools.diagnostics.Diagnostic; @@ -1425,7 +1426,7 @@ private Project createProject(Path filePath, String operationName) { Project project; BuildOptions options = BuildOptions.builder() .setOffline(CommonUtil.COMPILE_OFFLINE) - .setSticky(true) + .setUpdatePolicy(UpdatePolicy.HARD) .build(); if (projectKind == ProjectKind.BUILD_PROJECT) { project = BuildProject.load(projectRoot, options); diff --git a/misc/semver-checker/modules/semver-checker-core/src/main/java/io/ballerina/semver/checker/util/PackageUtils.java b/misc/semver-checker/modules/semver-checker-core/src/main/java/io/ballerina/semver/checker/util/PackageUtils.java index 150885d3f8f9..c5f500019644 100644 --- a/misc/semver-checker/modules/semver-checker-core/src/main/java/io/ballerina/semver/checker/util/PackageUtils.java +++ b/misc/semver-checker/modules/semver-checker-core/src/main/java/io/ballerina/semver/checker/util/PackageUtils.java @@ -26,6 +26,7 @@ import io.ballerina.projects.directory.BuildProject; import io.ballerina.projects.directory.ProjectLoader; import io.ballerina.projects.directory.SingleFileProject; +import io.ballerina.projects.environment.UpdatePolicy; import io.ballerina.projects.util.ProjectConstants; import io.ballerina.projects.util.ProjectPaths; import io.ballerina.semver.checker.exception.SemverToolException; @@ -114,7 +115,7 @@ private static BuildOptions constructDefaultBuildOptions() { .setSkipTests(true) .setTestReport(false) .setObservabilityIncluded(false) - .setSticky(false) + .setUpdatePolicy(UpdatePolicy.SOFT) .setDumpGraph(false) .setDumpRawGraphs(false) .setConfigSchemaGen(false)