Skip to content

Commit 2cd695b

Browse files
authored
Add general purpose download task (#1262)
* Add general purpose download task * Use duration + add basic max age test * Enable default caching
1 parent e3cd494 commit 2cd695b

File tree

2 files changed

+275
-0
lines changed

2 files changed

+275
-0
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* This file is part of fabric-loom, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) 2025 FabricMC
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package net.fabricmc.loom.task;
26+
27+
import java.net.URISyntaxException;
28+
import java.time.Duration;
29+
30+
import javax.inject.Inject;
31+
32+
import org.gradle.api.DefaultTask;
33+
import org.gradle.api.file.RegularFileProperty;
34+
import org.gradle.api.provider.Property;
35+
import org.gradle.api.tasks.Input;
36+
import org.gradle.api.tasks.Optional;
37+
import org.gradle.api.tasks.OutputFile;
38+
import org.gradle.api.tasks.TaskAction;
39+
import org.gradle.workers.WorkAction;
40+
import org.gradle.workers.WorkParameters;
41+
import org.gradle.workers.WorkQueue;
42+
import org.gradle.workers.WorkerExecutor;
43+
import org.jetbrains.annotations.ApiStatus;
44+
45+
import net.fabricmc.loom.util.ExceptionUtil;
46+
import net.fabricmc.loom.util.download.Download;
47+
import net.fabricmc.loom.util.download.DownloadBuilder;
48+
import net.fabricmc.loom.util.download.DownloadException;
49+
50+
/**
51+
* A general purpose task for downloading files from a URL, using the loom {@link Download} utility.
52+
*/
53+
public abstract class DownloadTask extends DefaultTask {
54+
/**
55+
* The URL to download the file from.
56+
*/
57+
@Input
58+
public abstract Property<String> getUrl();
59+
60+
/**
61+
* The expected SHA-1 hash of the downloaded file.
62+
*/
63+
@Optional
64+
@Input
65+
public abstract Property<String> getSha1();
66+
67+
/**
68+
* The maximum age of the downloaded file in days. When not provided the downloaded file will never be considered stale.
69+
*/
70+
@Optional
71+
@Input
72+
public abstract Property<Duration> getMaxAge();
73+
74+
/**
75+
* The file to download to.
76+
*/
77+
@OutputFile
78+
public abstract RegularFileProperty getOutput();
79+
80+
// Internal stuff:
81+
82+
@ApiStatus.Internal
83+
@Input
84+
protected abstract Property<Boolean> getIsOffline();
85+
86+
@Inject
87+
protected abstract WorkerExecutor getWorkerExecutor();
88+
89+
@Inject
90+
public DownloadTask() {
91+
getIsOffline().set(getProject().getGradle().getStartParameter().isOffline());
92+
}
93+
94+
@TaskAction
95+
public void run() {
96+
final WorkQueue workQueue = getWorkerExecutor().noIsolation();
97+
98+
workQueue.submit(DownloadAction.class, params -> {
99+
params.getUrl().set(getUrl());
100+
params.getSha1().set(getSha1());
101+
params.getMaxAge().set(getMaxAge());
102+
params.getOutputFile().set(getOutput());
103+
params.getIsOffline().set(getIsOffline());
104+
});
105+
}
106+
107+
public interface DownloadWorkParameters extends WorkParameters {
108+
Property<String> getUrl();
109+
Property<String> getSha1();
110+
Property<Duration> getMaxAge();
111+
RegularFileProperty getOutputFile();
112+
Property<Boolean> getIsOffline();
113+
}
114+
115+
public abstract static class DownloadAction implements WorkAction<DownloadWorkParameters> {
116+
@Override
117+
public void execute() {
118+
DownloadBuilder builder;
119+
120+
try {
121+
builder = Download.create(getParameters().getUrl().get()).defaultCache();
122+
} catch (URISyntaxException e) {
123+
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Invalid URL", e);
124+
}
125+
126+
if (getParameters().getMaxAge().isPresent()) {
127+
builder.maxAge(getParameters().getMaxAge().get());
128+
}
129+
130+
if (getParameters().getSha1().isPresent()) {
131+
builder.sha1(getParameters().getSha1().get());
132+
}
133+
134+
if (getParameters().getIsOffline().get()) {
135+
builder.offline();
136+
}
137+
138+
try {
139+
builder.downloadPath(getParameters().getOutputFile().get().getAsFile().toPath());
140+
} catch (DownloadException e) {
141+
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to download file", e);
142+
}
143+
}
144+
}
145+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* This file is part of fabric-loom, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) 2025 FabricMC
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package net.fabricmc.loom.test.integration
26+
27+
import spock.lang.Unroll
28+
29+
import net.fabricmc.loom.test.unit.download.DownloadTest
30+
import net.fabricmc.loom.test.util.GradleProjectTestTrait
31+
32+
import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
33+
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
34+
35+
class DownloadTaskTest extends DownloadTest implements GradleProjectTestTrait {
36+
@Unroll
37+
def "download (gradle #version)"() {
38+
setup:
39+
server.get("/simpleFile") {
40+
it.result("Hello World")
41+
}
42+
43+
def gradle = gradleProject(project: "minimalBase", version: version)
44+
gradle.buildGradle << """
45+
dependencies {
46+
minecraft "com.mojang:minecraft:1.21.4"
47+
mappings "net.fabricmc:yarn:1.21.4+build.8:v2"
48+
}
49+
50+
tasks.register("download", net.fabricmc.loom.task.DownloadTask) {
51+
url = "${PATH}/simpleFile"
52+
output = file("out.txt")
53+
}
54+
"""
55+
when:
56+
def result = gradle.run(task: "download")
57+
def output = new File(gradle.projectDir, "out.txt")
58+
59+
then:
60+
result.task(":download").outcome == SUCCESS
61+
output.text == "Hello World"
62+
63+
where:
64+
version << STANDARD_TEST_VERSIONS
65+
}
66+
67+
@Unroll
68+
def "download sha1 (gradle #version)"() {
69+
setup:
70+
server.get("/simpleFile") {
71+
it.result("Hello World")
72+
}
73+
74+
def gradle = gradleProject(project: "minimalBase", version: version)
75+
gradle.buildGradle << """
76+
dependencies {
77+
minecraft "com.mojang:minecraft:1.21.4"
78+
mappings "net.fabricmc:yarn:1.21.4+build.8:v2"
79+
}
80+
81+
tasks.register("download", net.fabricmc.loom.task.DownloadTask) {
82+
url = "${PATH}/simpleFile"
83+
sha1 = "0a4d55a8d778e5022fab701977c5d840bbc486d0"
84+
output = file("out.txt")
85+
}
86+
"""
87+
when:
88+
def result = gradle.run(task: "download")
89+
def output = new File(gradle.projectDir, "out.txt")
90+
91+
then:
92+
result.task(":download").outcome == SUCCESS
93+
output.text == "Hello World"
94+
95+
where:
96+
version << STANDARD_TEST_VERSIONS
97+
}
98+
99+
@Unroll
100+
def "download max age (gradle #version)"() {
101+
setup:
102+
server.get("/simpleFile") {
103+
it.result("Hello World")
104+
}
105+
106+
def gradle = gradleProject(project: "minimalBase", version: version)
107+
gradle.buildGradle << """
108+
dependencies {
109+
minecraft "com.mojang:minecraft:1.21.4"
110+
mappings "net.fabricmc:yarn:1.21.4+build.8:v2"
111+
}
112+
113+
tasks.register("download", net.fabricmc.loom.task.DownloadTask) {
114+
url = "${PATH}/simpleFile"
115+
maxAge = Duration.ofDays(1)
116+
output = file("out.txt")
117+
}
118+
"""
119+
when:
120+
def result = gradle.run(task: "download")
121+
def output = new File(gradle.projectDir, "out.txt")
122+
123+
then:
124+
result.task(":download").outcome == SUCCESS
125+
output.text == "Hello World"
126+
127+
where:
128+
version << STANDARD_TEST_VERSIONS
129+
}
130+
}

0 commit comments

Comments
 (0)