diff --git a/.github/scripts/android_studio_releases.main.kts b/.github/scripts/android_studio_releases.main.kts index a9430aa8d..54b1d134b 100755 --- a/.github/scripts/android_studio_releases.main.kts +++ b/.github/scripts/android_studio_releases.main.kts @@ -2,31 +2,23 @@ /** * This script is used to update the Android Studio releases page. - * At first, it fetches the list of Android Studio updates from the official `updates.xml` file. + * At first, it fetches the list of Android Studio updates from an XML file generated on TeamCity. * Parsed list is used to generate the Markdown table. - * The actual IntelliJ IDEA release version is obtained with the help of the JetBrains Data Services API. */ -@file:DependsOn("org.jsoup:jsoup:1.14.3") @file:DependsOn("net.swiftzer.semver:semver:1.1.2") @file:DependsOn("org.simpleframework:simple-xml:2.7.1") -@file:DependsOn("org.json:json:20211205") import net.swiftzer.semver.SemVer -import org.jsoup.Jsoup import org.simpleframework.xml.Attribute +import org.simpleframework.xml.Element import org.simpleframework.xml.ElementList import org.simpleframework.xml.Root import org.simpleframework.xml.core.Persister -import java.io.BufferedInputStream import java.io.File -import java.io.FileOutputStream import java.net.URL -import java.util.zip.ZipFile +val RELEASES_LIST = "https://jb.gg/android-studio-releases-list.xml" val RELEASES_FILE_PATH_MD = "topics/_generated/android_studio_releases.md" -val RELEASES_FILE_PATH_XML = "topics/_generated/android_studio_releases.xml" -val INTELLIJ_RELEASES = "https://www.jetbrains.com/intellij-repository/releases/" -val ANDROID_STUDIO_HOST = "https://developer.android.com" val CHANNEL_BADGES_LIST = """ [release]: https://img.shields.io/badge/-release-blue?style=flat-square [patch]: https://img.shields.io/badge/-patch-orange?style=flat-square @@ -36,81 +28,30 @@ val CHANNEL_BADGES_LIST = """ [preview]: https://img.shields.io/badge/-preview-darktgrey?style=flat-square """ -val platformBuildToVersionMapping = INTELLIJ_RELEASES.fetch { content -> - Jsoup.parse(content, "").select("h2:contains(com.jetbrains.intellij.idea) + table tbody tr").mapNotNull { tr -> - val (version, build) = tr.select("td:nth-child(odd)").map { SemVer.parse(it.text()) } - (build to version).takeIf { version.major > 2000 } - }.toMap().toSortedMap() -} +val content = URL(RELEASES_LIST).readText() + .run { Persister().read(Content::class.java, this) } + ?: throw RuntimeException("Failed to parse releases list") -val frameUrl = "$ANDROID_STUDIO_HOST/studio/archive".fetch { content -> - Jsoup.parse(content, "").select("devsite-iframe iframe[src]").firstOrNull()?.attr("src") -}.let { "$ANDROID_STUDIO_HOST/$it" } +val xml = """ -frameUrl.fetch { content -> - val contentFile = file(RELEASES_FILE_PATH_XML) - val current = contentFile.takeIf { it.length() > 0 }?.let { - Persister().read(Content::class.java, it) - } ?: Content() - val nameToBuildMapping = current.items.associate { it.name to it.build } - - Jsoup.parse(content, "").select("section.expandable").run { - mapIndexed { index, item -> - val title = item.select("p").firstOrNull()?.text() ?: throw IllegalStateException("No title found") - val (name, channelRaw, date) = """^([\w ]+ \(?[\d.]+\)? ?(?:(\w+) \d+)?) (\w+ \d+, \d+)$""".toRegex().find(title)?.groupValues?.drop(1) - ?: emptyList() - - println("# $name") - println(" ${index + 1}/$size") - - val href = item.select(".downloads a[href$=.zip]").firstOrNull()?.attr("href") - val version = href?.split('/')?.let { it[it.indexOf("ide-zips") + 1] } - ?: throw IllegalStateException("No version found for $name") - val build = nameToBuildMapping[name].takeUnless(String?::isNullOrBlank) ?: run { href.resolveBuild() } - val platformBuild = build.split('-').last().toLooseVersion() - - val platformVersion = platformBuildToVersionMapping[platformBuild] ?: run { - platformBuildToVersionMapping.entries.findLast { it.key < platformBuild }?.value - } - val channel = channelRaw.takeIf { it.isNotBlank() } ?: "Release" - - println(" version='${version}'") - println(" build='${build}'") - println(" platformBuild='${platformBuild}'") - println(" platformVersion='${platformVersion}'") - - Item(name, build, version, channel, platformBuild.toString(), platformVersion.toString(), date) - } - }.let { - val version = with(current) { - when (items.hashCode() != it.hashCode()) { - true -> version + 1 - false -> version - } - } - Content(version, it) - }.also { - Persister().write(it, contentFile) - }.also { (_, items) -> - (""" - - """ + items.groupBy { it.version.toLooseVersion().major }.entries.joinToString("\n\n") { - """ - ## ${it.key}.* - - ${it.value.renderTable()} - """ - } + """ - $CHANNEL_BADGES_LIST - - - - ${items.distinctBy(Item::version).take(5).renderTable()} - $CHANNEL_BADGES_LIST - - """).split("\n").joinToString("\n", transform = String::trim).let(file(RELEASES_FILE_PATH_MD)::writeText) + +${ + content.items.groupBy { it.version.toLooseVersion().major }.entries.joinToString("\n\n") { + """ + ## ${it.key}.* + ${it.value.renderTable()} + """ } } +$CHANNEL_BADGES_LIST + + + +${content.items.distinctBy(Item::version).take(5).renderTable()} +$CHANNEL_BADGES_LIST + + +""".split("\n").joinToString("\n", transform = String::trim).let(file(RELEASES_FILE_PATH_MD)::writeText) fun List.renderTable() = """ | Release Name | Channel | Release Date | Version | IntelliJ IDEA Version | @@ -125,41 +66,6 @@ fun List.renderTable() = """ "| $name | $channel | $date | $version | $platform |" } -fun String.fetch(block: (String) -> T) = URL(this).openStream().use { inputStream -> - block(inputStream.readBytes().toString(Charsets.UTF_8)) -} - -fun String.download(block: (File) -> T) = URL(this).openStream().use { inputStream -> - BufferedInputStream(inputStream).use { bis -> - File.createTempFile("android-studio", ".zip").also(File::deleteOnExit).let { tempFile -> - FileOutputStream(tempFile).use { outputStream -> - println(" Downloading $this to $tempFile") - ByteArray(1024).let { data -> - var count: Int - while (bis.read(data, 0, data.size).also { count = it } != -1) { - outputStream.write(data, 0, count) - } - } - } - block(tempFile) - } - } -} - -fun String.resolveBuild() = download { file -> - ZipFile(file).use { zip -> - zip.getEntry("android-studio/build.txt").let { entry -> - zip.getInputStream(entry).use { inputStream -> - inputStream.readBytes().toString(Charsets.UTF_8) - }.also { - println(" Resolved build number: $it") - } - } - }.also { - file.delete() - } -} - fun String.toLooseVersion() = split('.').map { it.take(4).toInt() }.let { val (major, minor, patch) = it + 0 SemVer(major, minor, patch) @@ -167,18 +73,48 @@ fun String.toLooseVersion() = split('.').map { it.take(4).toInt() }.let { fun file(path: String) = File(System.getenv("GITHUB_WORKSPACE") ?: "../../").resolve(path).also(File::createNewFile) -@Root(strict = false) +@Root(strict = false, name = "content") data class Content( - @field:Attribute var version: Int = 1, - @field:ElementList(inline = true, required = false) var items: List = mutableListOf(), + @field:Attribute + var version: Int = 1, + + @field:ElementList(inline = true, entry = "item") + var items: List = mutableListOf(), ) data class Item( + @field:Element var name: String = "", + + @field:Element var build: String = "", + + @field:Element var version: String = "", + + @field:Element var channel: String = "", + + @field:Element var platformBuild: String? = null, + + @field:Element var platformVersion: String? = null, + + @field:Element var date: String = "", + + @field:ElementList(inline = true, entry = "download") + var downloads: List = mutableListOf(), +) + +data class Download( + @field:Element + var link: String = "", + + @field:Element + var size: Int = 0, + + @field:Element + var checksum: String = "", ) diff --git a/topics/_generated/android_studio_releases.md b/topics/_generated/android_studio_releases.md index 35b88bac7..4e1fc84cc 100644 --- a/topics/_generated/android_studio_releases.md +++ b/topics/_generated/android_studio_releases.md @@ -1,9 +1,9 @@ + ## 2022.* - | Release Name | Channel | Release Date | Version | IntelliJ IDEA Version | |--------------|:-------:|--------------|---------|-----------------------| | Electric Eel (2022.1.1) Canary 9 | ![canary][canary] | August 3, 2022 | **2022.1.1.9**
AI-221.5921.22.2211.8881706 | **2022.1.3**
221.5921.22 | @@ -20,7 +20,6 @@ ## 2021.* - | Release Name | Channel | Release Date | Version | IntelliJ IDEA Version | |--------------|:-------:|--------------|---------|-----------------------| | Dolphin (2021.3.1) Beta 5 | ![beta][beta] | July 7, 2022 | **2021.3.1.14**
AI-213.7172.25.2113.8774922 | **2021.3.3**
213.7172.25 | @@ -80,7 +79,6 @@ ## 2020.* - | Release Name | Channel | Release Date | Version | IntelliJ IDEA Version | |--------------|:-------:|--------------|---------|-----------------------| | Arctic Fox (2020.3.1) Patch 4 | ![patch][patch] | December 8, 2021 | **2020.3.1.26**
AI-203.7717.56.2031.7935034 | **2020.3.3**
203.7717.56 | @@ -114,7 +112,6 @@ ## 4.* - | Release Name | Channel | Release Date | Version | IntelliJ IDEA Version | |--------------|:-------:|--------------|---------|-----------------------| | 4.2.2 | ![release][release] | June 30, 2021 | **4.2.2.0**
AI-202.7660.26.42.7486908 | **2020.2.3**
202.7660.26 | @@ -188,7 +185,6 @@ ## 3.* - | Release Name | Channel | Release Date | Version | IntelliJ IDEA Version | |--------------|:-------:|--------------|---------|-----------------------| | 3.6.3 | ![release][release] | April 17, 2020 | **3.6.3.0**
AI-192.7142.36.36.6392135 | **2019.2.4**
192.7142.36 | @@ -357,7 +353,6 @@ ## 2.* - | Release Name | Channel | Release Date | Version | IntelliJ IDEA Version | |--------------|:-------:|--------------|---------|-----------------------| | 2.4 Preview 7 | ![preview][preview] | April 28, 2017 | **2.4.0.6**
AI-171.3934896 | **2017.1.0**
171.3934.0 | @@ -395,3 +390,4 @@ [preview]: https://img.shields.io/badge/-preview-darktgrey?style=flat-square
+