1 import org.gradle.api.*
2 import org.gradle.api.artifacts.dsl.*
3 import org.gradle.api.artifacts.repositories.*
4 import org.gradle.api.initialization.dsl.*
5 import org.gradle.kotlin.dsl.*
6 import org.jetbrains.kotlin.gradle.targets.js.nodejs.*
7 import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.*
8 import org.jetbrains.kotlin.gradle.targets.js.yarn.*
9 import java.net.*
10
11 /**
12 * Enabled via environment variable, so that it can be reliably accessed from any piece of the build script,
13 * including buildSrc within TeamCity CI.
14 */
15 private val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolean() == true
16
17 /**
18 * The list of repositories supported by cache redirector should be synced with the list at https://cache-redirector.jetbrains.com/redirects_generated.html
19 * To add a repository to the list create an issue in ADM project (example issue https://youtrack.jetbrains.com/issue/IJI-149)
20 */
21 private val mirroredUrls = listOf(
22 "https://cdn.azul.com/zulu/bin",
23 "https://clojars.org/repo",
24 "https://dl.google.com/android/repository",
25 "https://dl.google.com/dl/android/maven2",
26 "https://dl.google.com/dl/android/studio/ide-zips",
27 "https://dl.google.com/go",
28 "https://download.jetbrains.com",
29 "https://github.com/yarnpkg/yarn/releases/download",
30 "https://jitpack.io",
31 "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap",
32 "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev",
33 "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/eap",
34 "https://nodejs.org/dist",
35 "https://oss.sonatype.org/content/repositories/releases",
36 "https://oss.sonatype.org/content/repositories/snapshots",
37 "https://oss.sonatype.org/content/repositories/staging",
38 "https://packages.confluent.io/maven/",
39 "https://plugins.gradle.org/m2",
40 "https://plugins.jetbrains.com/maven",
41 "https://repo.grails.org/grails/core",
42 "https://repo.jenkins-ci.org/releases",
43 "https://repo.maven.apache.org/maven2",
44 "https://repo.spring.io/milestone",
45 "https://repo.typesafe.com/typesafe/ivy-releases",
46 "https://repo1.maven.org/maven2",
47 "https://services.gradle.org",
48 "https://www.exasol.com/artifactory/exasol-releases",
49 "https://www.jetbrains.com/intellij-repository/nightly",
50 "https://www.jetbrains.com/intellij-repository/releases",
51 "https://www.jetbrains.com/intellij-repository/snapshots",
52 "https://www.myget.org/F/intellij-go-snapshots/maven",
53 "https://www.myget.org/F/rd-model-snapshots/maven",
54 "https://www.myget.org/F/rd-snapshots/maven",
55 "https://www.python.org/ftp",
56 )
57
58 private val aliases = mapOf(
59 "https://repo.maven.apache.org/maven2" to "https://repo1.maven.org/maven2" // Maven Central
60 )
61
toCacheRedirectorUrinull62 private fun URI.toCacheRedirectorUri() = URI("https://cache-redirector.jetbrains.com/$host/$path")
63
64 private fun URI.maybeRedirect(): URI? {
65 val url = toString().trimEnd('/')
66 val dealiasedUrl = aliases.getOrDefault(url, url)
67
68 return if (mirroredUrls.any { dealiasedUrl.startsWith(it) }) {
69 URI(dealiasedUrl).toCacheRedirectorUri()
70 } else {
71 null
72 }
73 }
74
isCachedOrLocalnull75 private fun URI.isCachedOrLocal() = scheme == "file" ||
76 host == "cache-redirector.jetbrains.com" ||
77 host == "teamcity.jetbrains.com" ||
78 host == "buildserver.labs.intellij.net"
79
80 private fun Project.checkRedirectUrl(url: URI, containerName: String): URI {
81 val redirected = url.maybeRedirect()
82 if (redirected == null && !url.isCachedOrLocal()) {
83 val msg = "Repository $url in $containerName should be cached with cache-redirector"
84 val details = "Using non cached repository may lead to download failures in CI builds." +
85 " Check buildSrc/src/main/kotlin/CacheRedirector.kt for details."
86 logger.warn("WARNING - $msg\n$details")
87 }
88 return if (cacheRedirectorEnabled) redirected ?: url else url
89 }
90
Projectnull91 private fun Project.checkRedirect(repositories: RepositoryHandler, containerName: String) {
92 if (cacheRedirectorEnabled) {
93 logger.info("Redirecting repositories for $containerName")
94 }
95 for (repository in repositories) {
96 when (repository) {
97 is MavenArtifactRepository -> repository.url = checkRedirectUrl(repository.url, containerName)
98 is IvyArtifactRepository -> repository.url = checkRedirectUrl(repository.url, containerName)
99 }
100 }
101 }
102
Projectnull103 private fun Project.configureYarnAndNodeRedirects() {
104 if (CacheRedirector.isEnabled) {
105 val yarnRootExtension = extensions.findByType<YarnRootExtension>()
106 yarnRootExtension?.downloadBaseUrl?.let {
107 yarnRootExtension.downloadBaseUrl = CacheRedirector.maybeRedirect(it)
108 }
109
110 val nodeJsExtension = rootProject.extensions.findByType<NodeJsRootExtension>()
111 nodeJsExtension?.nodeDownloadBaseUrl?.let {
112 nodeJsExtension.nodeDownloadBaseUrl = CacheRedirector.maybeRedirect(it)
113 }
114 }
115 }
116
117 // Used from Groovy scripts
118 // TODO get rid of Groovy, come up with a proper convention for rootProject vs arbitrary project argument
119 object CacheRedirector {
120 /**
121 * Substitutes repositories in buildScript { } block.
122 */
123 @JvmStatic
ScriptHandlernull124 fun ScriptHandler.configureBuildScript(rootProject: Project) {
125 rootProject.checkRedirect(repositories, "${rootProject.displayName} buildscript")
126 }
127
128 @JvmStatic
configurenull129 fun configure(project: Project) {
130 project.checkRedirect(project.repositories, project.displayName)
131 }
132
133 /**
134 * Configures JS-specific extensions to use
135 */
136 @JvmStatic
configureJsPackageManagersnull137 fun configureJsPackageManagers(project: Project) {
138 project.configureYarnAndNodeRedirects()
139 }
140
141 /**
142 * Temporary repositories to depend on until GC milestone 4 in KGP
143 * and stable Node release. Safe to remove when its removal does not break WASM tests.
144 */
145 @JvmStatic
configureWasmNodeRepositoriesnull146 fun configureWasmNodeRepositories(project: Project) {
147 val extension = project.extensions.findByType<NodeJsRootExtension>()
148 if (extension != null) {
149 extension.nodeVersion = "21.0.0-v8-canary202309167e82ab1fa2"
150 extension.nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary"
151 }
152
153 project.tasks.withType<KotlinNpmInstallTask>().configureEach {
154 args.add("--ignore-engines")
155 }
156 }
157
158 @JvmStatic
maybeRedirectnull159 fun maybeRedirect(url: String): String {
160 if (!cacheRedirectorEnabled) return url
161 return URI(url).maybeRedirect()?.toString() ?: url
162 }
163
164 @JvmStatic
165 val isEnabled get() = cacheRedirectorEnabled
166 }
167