Skip to content

Commit 4f61c3a

Browse files
maennchensschuberth
authored andcommitted
feat(gleam): Run 'gleam deps download' when no lockfile exists
When 'allowDynamicVersions' is enabled and no manifest.toml lockfile exists, run 'gleam deps download' to generate it before resolving dependencies. This provides a fallback for projects that haven't committed their lockfile. A WARNING issue is added to indicate the results may not be reproducible. If the command fails, an ERROR issue with the exit code and stderr is reported. Closes #11245. Signed-off-by: Jonatan Männchen <jonatan@maennchen.ch>
1 parent 45a5854 commit 4f61c3a

5 files changed

Lines changed: 84 additions & 44 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name = "broken_project"
2+
version = "1.0.0"
3+
4+
[dependencies]
5+
nonexistent_package_that_does_not_exist = ">= 99.99.99"

plugins/package-managers/gleam/src/funTest/assets/projects/synthetic/no-lockfile-expected-output-allow-dynamic-versions.yml

Lines changed: 0 additions & 27 deletions
This file was deleted.

plugins/package-managers/gleam/src/funTest/kotlin/GleamFunTest.kt

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,17 @@ package org.ossreviewtoolkit.plugins.packagemanagers.gleam
2222
import com.github.tomakehurst.wiremock.WireMockServer
2323
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
2424

25+
import io.kotest.core.annotation.Tags
2526
import io.kotest.core.spec.style.StringSpec
27+
import io.kotest.engine.spec.tempdir
28+
import io.kotest.matchers.collections.beEmpty
2629
import io.kotest.matchers.collections.containExactly
30+
import io.kotest.matchers.collections.shouldBeSingleton
31+
import io.kotest.matchers.collections.shouldContain
2732
import io.kotest.matchers.should
33+
import io.kotest.matchers.shouldBe
34+
import io.kotest.matchers.shouldNot
35+
import io.kotest.matchers.string.shouldContain
2836
import io.kotest.property.Arb
2937
import io.kotest.property.arbitrary.file
3038
import io.kotest.property.arbitrary.single
@@ -35,12 +43,14 @@ import org.ossreviewtoolkit.analyzer.analyze
3543
import org.ossreviewtoolkit.analyzer.getAnalyzerResult
3644
import org.ossreviewtoolkit.analyzer.resolveSingleProject
3745
import org.ossreviewtoolkit.analyzer.withInvariantIssues
46+
import org.ossreviewtoolkit.model.Severity
3847
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
3948
import org.ossreviewtoolkit.model.toYaml
4049
import org.ossreviewtoolkit.utils.common.div
4150
import org.ossreviewtoolkit.utils.test.getAssetFile
4251
import org.ossreviewtoolkit.utils.test.matchExpectedResult
4352

53+
@Tags("RequiresExternalTool")
4454
class GleamFunTest : StringSpec({
4555
val server = WireMockServer(
4656
WireMockConfiguration.options()
@@ -88,17 +98,36 @@ class GleamFunTest : StringSpec({
8898
}
8999

90100
"Resolve dependencies for a project without a lockfile if 'allowDynamicVersions' is enabled" {
91-
val definitionFile = getAssetFile("projects/synthetic/no-lockfile/gleam.toml")
92-
val expectedResultFile =
93-
getAssetFile("projects/synthetic/no-lockfile-expected-output-allow-dynamic-versions.yml")
101+
val projectDir = tempdir()
102+
getAssetFile("projects/synthetic/no-lockfile").copyRecursively(projectDir)
94103

95104
val result = createGleam().resolveSingleProject(
96-
definitionFile,
105+
projectDir.resolve("gleam.toml"),
97106
resolveScopes = true,
98107
allowDynamicVersions = true
99108
)
100109

101-
result.withInvariantIssues().toYaml() should matchExpectedResult(expectedResultFile, definitionFile)
110+
result.packages shouldNot beEmpty()
111+
result.packages.map { it.id.name } shouldContain "gleam_stdlib"
112+
}
113+
114+
"Resolve dependencies fails with error details for broken gleam.toml" {
115+
val projectDir = tempdir()
116+
getAssetFile("projects/synthetic/no-lockfile-broken").copyRecursively(projectDir)
117+
118+
val result = createGleam().resolveSingleProject(
119+
projectDir.resolve("gleam.toml"),
120+
resolveScopes = true,
121+
allowDynamicVersions = true
122+
)
123+
124+
result.issues.shouldBeSingleton {
125+
it.severity shouldBe Severity.ERROR
126+
it.message shouldContain "failed with exit code"
127+
it.message shouldContain "Dependency resolution failed"
128+
}
129+
130+
result.packages should beEmpty()
102131
}
103132

104133
"Resolve dependencies for a project with no dependencies correctly" {

plugins/package-managers/gleam/src/main/kotlin/Gleam.kt

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,7 @@ class Gleam internal constructor(
112112

113113
val project = createProject(definitionFile, gleamToml)
114114

115-
val manifest = if (manifestFile.isFile) {
116-
parseManifest(manifestFile)
117-
} else if (!hasDependencies) {
118-
GleamManifest.EMPTY
119-
} else {
120-
issues += Issue(
121-
source = projectType,
122-
message = "No lockfile found. No dependencies were resolved. " +
123-
"Run 'gleam deps download' to generate a manifest.toml lockfile."
124-
)
125-
GleamManifest.EMPTY
126-
}
115+
val manifest = resolveManifest(manifestFile, hasDependencies, workingDir)
127116

128117
val context = GleamProjectContext(
129118
hexClient = hexApiClientFactory(),
@@ -159,6 +148,18 @@ class Gleam internal constructor(
159148
override fun createPackageManagerResult(projectResults: Map<File, List<ProjectAnalyzerResult>>) =
160149
PackageManagerResult(projectResults, graphBuilder.build(), graphBuilder.packages())
161150

151+
private fun resolveManifest(manifestFile: File, hasDependencies: Boolean, workingDir: File): GleamManifest =
152+
when {
153+
manifestFile.isFile -> parseManifest(manifestFile)
154+
155+
!hasDependencies -> GleamManifest.EMPTY
156+
157+
else -> {
158+
GleamCommand.run(workingDir, "deps", "download").requireSuccess()
159+
parseManifest(manifestFile)
160+
}
161+
}
162+
162163
private fun createProject(definitionFile: File, gleamToml: GleamToml): Project {
163164
val workingDir = definitionFile.parentFile
164165

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (C) 2025 The ORT Project Copyright Holders <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.ossreviewtoolkit.plugins.packagemanagers.gleam
21+
22+
import java.io.File
23+
24+
import org.ossreviewtoolkit.utils.common.CommandLineTool
25+
26+
internal object GleamCommand : CommandLineTool {
27+
override fun command(workingDir: File?) = "gleam"
28+
29+
override fun getVersionArguments() = "--version"
30+
31+
override fun transformVersion(output: String) = output.removePrefix("gleam ").trim()
32+
}

0 commit comments

Comments
 (0)