教程|使用 TeamCity 为 Android 项目构建 CI/CD 管道

本文是 draft.dev 的 Kumar Harsh 撰写的客座文章。

如果您经常发布 Android 应用,您可能已经了解定义明确的构建、测试和部署工作流的价值。如果没有灵活的自动化 DevOps 工作流,就很难维持高速发布。通过持续集成和持续部署 (CI/CD) 自动执行这些工作流可以让您的工作变得更加轻松,让您更早地发现 bug,更快地发布产品。

JetBrains TeamCity 是一个用于构建可靠管道的 CI/CD 平台。它可与流行的 Android 开发工具无缝集成,并具有人性化界面来配置构建和测试的各个阶段。

本文将向您介绍如何使用 JetBrains TeamCity 为您的 Android 项目设置 CI/CD 管道。您将探索 Android CI/CD 管道的关键组件,并深入了解如何使用 TeamCity 配置一些示例管道。


了解 Android CI/CD 管道

Android 开发的有效 CI/CD 管道包含标准 DevOps 管道中的所有步骤,并通过附加流程(如工件签名和自动部署到 Google Play 商店的内部轨道)对其进行了增强。下面是典型 Android CI/CD 管道中涉及的所有步骤的快速概览:

1. 代码签出和版本控制集成:管道会先从您的版本控制系统(VCS,如 Git)获取最新的代码更改。如果您在使用 TeamCity,您将受益于它与流行的版本控制工具的集成以及在代码提交或合并时自动触发管道的功能。

2. 使用 Gradle 实现构建自动化:Android 项目的标准构建工具 Gradle 是这一步的核心。TeamCity 会执行 Gradle 命令来编译您的代码、汇集资源并生成构建工件。TeamCity 的构建运行程序提供了与不同 Gradle 版本的兼容性和可自定义的构建配置环境。

3. 针对多个 Android 版本/平台的单元和集成测试:下一步包括运行验证个别代码模块的单元测试和检查不同组件如何交互的集成测试(包括 UI 测试)。TeamCity 允许您配置多个测试运行程序和框架(例如 JUnit、Espresso),并使用模拟器或设备实验室针对各种 Android 版本和平台执行测试。

4. 静态代码分析(以 JetBrains Qodana 为特色)和代码覆盖率报告:静态代码分析有助于尽早识别潜在的 bug、安全漏洞和代码样式不一致。TeamCity 与 JetBrains Qodana 集成,后者是一款静态代码分析工具,具有代码异味检测、复杂代码分析以及与各种编程语言集成等功能,可以确保全面的代码质量检查。此外,TeamCity 可以生成代码覆盖率报告,指示单元测试执行了代码的哪些部分。这有助于开发者重点关注覆盖率较弱的区域。

5. 工件生成和签名(APK 和 AAB):成功构建和测试后,下一步涉及创建可部署工件。对于 Android 应用,这通常涉及生成已签名的 Android Package Kits (APK) 或 Android App Bundles (AAB)。TeamCity 允许您使用构建步骤在管道内自动执行签名流程。

6. 部署到内部测试和生产环境(Google Play,Beta 渠道):CI/CD 管道可以将应用自动部署到各种环境。TeamCity 允许配置部署到内部测试平台或直接部署到 Google Play 上的生产渠道。

7. 持续监控和反馈循环:可靠的 CI/CD 管道不会随着部署而结束。TeamCity 集成了监控工具,使您能够跟踪应用性能、识别崩溃,以及收集用户反馈。此反馈循环使开发者能够对问题做出快速反应,并持续提高应用质量。


使用 TeamCity 构建管道

现在,您已经了解一般的 CI/CD 管道结构,我们来使用 TeamCity 构建一个管道。以下各部分将指导您设置 TeamCity、创建针对您的 Android 项目量身定制的构建配置、运行集成自动化测试,以及最后配置应用的打包和部署。

为了简单起见,本教程使用云托管版本的 TeamCity,该版本提供 14 天的免费试用。您可以使用 GitHub、GitLab、Bitbucket 或 Google 账号注册,也可以使用老式的电子邮件地址和密码组合注册。在进入下一步之前,请务必激活试用版或订阅。

不过,您也可以使用 TeamCity Cloud 和自托管构建代理,甚至 TeamCity On-Premises 来实现本教程的目的。请记住,使用自托管构建代理或 TeamCity On-Premises 需要您在代理上单独安装 Android SDK。

设置 TeamCity

访问 TeamCity Cloud 实例后,您看到的初始视图将如下所示:

TeamCity Cloud 仪表板

要开始处理 Android 项目,请点击页面中间的 Create project…(创建项目…)。然后,系统会要求您提供项目源代码的链接。如果您使用 Git 托管服务提供商(如 GitHub 或 Bitbucket Cloud)注册,请随时查看其相应部分,并使用专门的项目创建流程。

不过,如果您有仓库 URL,可以直接在 From a repository URL(从仓库 URL)标签页中使用。TeamCity 会自动检测 Git 托管服务提供商,并从中拉取项目。

Create Project(创建项目)页面

如果您手头没有 Android 项目,可以使用以下仓库学习本教程:

bash
https://github.com/krharsh17/android-app-teamcity

如果您要访问的仓库是私有仓库,或使用用户名和密码组合进行了加密,您可以在此处提供相应的用户名和密码组合,以便 TeamCity 能够访问该仓库。输入仓库 URL(以及所需的其他详细信息)后,点击 Proceed(继续)。

在下一页上,TeamCity Cloud 将验证与 VCS 仓库的连接。验证成功后,TeamCity 将拉取一些与项目相关的元数据,例如名称、默认分支等。您可以在将这些值存储到 TeamCity 项目之前对其进行修改。

创建项目时验证连接

确认此页面上的信息正确无误后,点击 Proceed(继续)按钮。然后,TeamCity 将开始根据仓库中的可用配置文件自动检测适用于该仓库的任何构建步骤。由于此仓库中具有基于 Gradle 的配置文件,TeamCity 会自动建议一组 Gradle 任务(在本例中为 cleanbuild)。

选中 Gradle 构建步骤旁边的复选框,然后点击 Use selected(使用所选):

选择自动建议的构建步骤

完成后,会出现一个小横幅,上面写着您现在可以运行项目中的第一个构建。点击右上角的 Run(运行)开始第一个构建:

开始您的第一个构建

点击按钮后,构建将加入队列,等待构建代理变得可用。您可以点击顶部导航窗格中的 Projects(项目),然后选择正在运行的构建,以查看其属性和状态:

正在运行的构建的详细信息

构建大约 5-6 分钟即可完成。恭喜!您已使用 TeamCity 设置了第一个 Android CI/CD 管道。此外,由于您使用了 VCS 仓库 URL 来设置此管道,它已被配置为以固定的时间间隔自动轮询仓库 URL,以查看是否有新的更改被推送到仓库。如果发现新的更改,管道会自动拉取最新的提交,并再次运行构建。

您还可以通过设置针对特定平台的 Web 挂钩来进一步增强此功能。例如,您刚刚设置的仓库托管在 GitHub 上。TeamCity 允许您方便地安装 GitHub Web 挂钩,这样每次仓库有活动时,GitHub 就会自动向 TeamCity 发送通知:

安装 GitHub Web 挂钩

如果愿意,您可以进行以上操作。不过,在本教程中并不需要这样做。

配置构建工件

您设置的仓库包括两种版本(FreePaid)。这两种版本各有两种构建变体(debugrelease)。这意味着 build 任务的结果将包括四个二进制文件,每种可能的版本和变体组合对应一个文件。我们来配置管道,以便在管道运行结束后提取这些工件并使其可供访问。

为此,请点击顶部导航窗格中的 Projects(项目),然后点击 Android App Teamcity (Android 应用 Teamcity)下的 Build(构建),打开标题为Build(构建)的构建配置的详细信息页面:

导航到构建配置页面

在这里,点击屏幕右上角的 Edit configuration(编辑配置)按钮:

编辑构建配置

您可以在这里配置构建配置的常规设置。您会注意到在列表的底部有一个标题为 Artifact paths(工件路径)的字段。您需要在这里定义希望在构建完成运行后提取和保留工件的路径:

设置工件路径

运行 Gradle build 任务时,Gradle 生成的工件会存储在 app/build/outputs/apk 中。因此,您需要在 Artifact paths(工件路径)下输入以下内容:

app/build/outputs/apk/*/*/* => output

app/build/outputs/apk 后添加 /*/*/* 是因为构建后生成的 APK 二进制文件的完整路径如下所示:app/build/outputs/apk/<flavor>/<variant>/app-<flavor>-<variant>-unsigned.apk.

为了表示 <variant><flavor> 和二进制文件名的所有可能值,我们使用了通配符 *。 

=> 是 Ant 样式路径的一个特征,用于分隔输出和输入目录。output 是存储最终二进制文件的文件夹名称。

添加完成后,点击页面底部的 Save(保存)按钮。您将看到一个黄色横幅,显示您的更改已保存:

保存对构建配置所做的更改

现在,您可以使用页面右上角的 Run(运行)按钮再次尝试运行管道,以查看构建完成后生成的工件:

查看生成的工件

现在,您已经设置一个管道,每次有提交推送到仓库的 main 分支时,管道就会被触发。此管道会为项目中的所有版本-变体组合生成未签名的构建工件,运行单元测试,并使构建工件可供查看。

接下来,您将学习如何自定义测试。


自定义测试

如前所述,Gradle 任务 build 还负责对所有生成的构建工件运行单元测试。不过,在某些情况下,您可能只想在应用的几个变体上运行测试。在这种情况下,您需要将 clean build 任务替换为适合您的用例的任务。

例如,如果您要为应用的免费版本的 release 变体创建一个未签名的 APK,并对其运行单元测试,则应将 clean build 替换为 assembleFreeRelease testFreeReleaseUnitTest。为此,请点击顶部导航窗格中的 Projects(项目),然后点击 Android App Teamcity(Android 应用 Teamcity)下的 Build(构建)。在下一个页面上,与上一步的操作一样,点击右上角的 Edit configuration(编辑配置)按钮。

您应该已进入构建配置的 General Settings(常规设置)页面,您之前访问过该页面来配置工件路径。在左侧导航窗格中,点击 Build Step: Gradle(构建步骤: Gradle)。

导航到构建设置

这将打开 Build Steps(构建步骤)页面,您可以在该页面中修改此构建配置的构建步骤。点击第一个构建步骤(标题为 Gradle)右侧的 Edit(编辑):

编辑 Gradle 构建步骤

现在,您可以更新 Gradle tasks(Gradle 任务)字段,以更改将作为此构建的一部分执行的任务。将 clean build 替换为 assembleFreeRelease testFreeReleaseUnitTest

更新 Gradle 任务

现在,点击底部的 Save(保存)。保存更改后,点击右上角的 Run(运行)按钮。这将触发此构建配置的另一次运行。

构建运行完成后,您可以在构建运行详细信息页面的 Tests(测试)标签页中查看 TeamCity 生成的报告:

查看测试结果

您可以查看每个单元测试的运行时间,以及测试完成后是否留下了任何堆栈跟踪。您还可以点击测试最右侧的三个点,然后选择 Show test history(显示测试历史记录),将当前测试运行的性能与过去的运行进行比较:

比较测试运行历史记录

您可以将测试的调查指派给团队成员,并通过 TeamCity 本身跟踪其调查历史记录。如果愿意,您还可以点击测试概览页面上的 Download(下载)链接下载测试结果。

此仓库中的测试数量很少,因此几分钟内就完成了构建运行。不过,在实际项目中,通常会有成百上千个单元测试。在这种情况下,在同一个运行程序代理上一个接一个地运行所有这些测试将耗费大量时间。要解决这个问题,您可以使用 TeamCity 的并行测试构建功能。

TeamCity 能够通过将测试运行拆分到多个构建代理来并行处理,从而帮助您最大限度地减少运行所有测试所需的总时间。要进行设置,请点击构建运行详细信息页面上的 Edit configuration(编辑配置)按钮,然后点击左侧导航窗格上的 Build Features(构建功能):

导航到 Build Features(构建功能)页面

Build Features(构建功能)页面上,点击 + Add build feature(+ 添加构建功能)按钮。在打开的对话框中,从下拉菜单中选择 Parallel tests(并行测试):

搜索并行测试

您需要输入并行执行测试的最大批次数。输入介于 4 和 8 之间的值可从并行化中获得最大收益。

设置并行构建批次

完成后,点击 Save(保存)按钮。现在,您可以尝试为具有大量测试用例的仓库运行测试,亲眼看看性能上的差异!

管理多个构建

由于此应用有多种版本和变体,有必要利用 TeamCity 提供的矩阵构建功能,将每种变体-版本组合拆分为其自己的运行实例,从而加快管道构建速度。此外,这还允许您构建应用程序的特定组合,而不必构建所有或单个变体-版本组合。

为此,您需要创建一个新的构建配置。点击顶部导航窗格中的 Projects(项目),然后点击 Android App Teamcity(Android 应用 Teamcity)。在项目详细信息页面上,点击右上角的 Edit project…(编辑项目…)按钮:

导航到项目配置

General Settings(常规设置)页面上,点击 Build Configurations(构建配置)部分下方的 + Create build configuration(+ 创建构建配置)按钮:

创建新构建配置

这将带您进入 Create Build Configuration(创建构建配置)向导。在 Repository URL(仓库 URL)字段中输入与之前相同的仓库 URL (https://github.com/krharsh17/android-app-teamcity),然后点击 Proceed(继续):

输入仓库 URL

将下一页上的 Build configuration name(构建配置名称)设置为 Matrix Builds(矩阵构建),并在所有其他字段中保留默认值。然后,点击 Proceed(继续)按钮:

设置构建配置详细信息

TeamCity 将通知您已找到类似的 VCS 根。点击以下对话框中的 Use this(使用)按钮:

选择现有 VCS 根

这将确保 TeamCity 对这两种构建配置只轮询一次 VCS URL,以避免额外的性能开销。

构建配置完成后,您应该会收到确认其已创建的通知:

新构建配置已创建

这一次,不需要设置 clean build Gradle 任务,因此不要勾选此页面上的任何复选框。点击表上方的 configure build steps manually(手动配置构建步骤)链接。

您将进入 New Build Step(新建构建步骤)页面,您可以在该页面上为构建步骤选择首选运行程序:

选择构建运行程序

从此列表中选择 Gradle。在打开的下一个页面上,在 Gradle tasks(Gradle 任务)字段中输入 clean test%env.FLAVOR%%env.VARIANT%

输入 Gradle 任务

这将确保运行程序首先清理构建文件夹,然后为环境变量提供的版本和变体运行测试任务。例如,对于免费应用的 release 变体,任务将被称为 clean testFreeRelease

向下滚动并点击 Save(保存)按钮。然后,您将返回 Build Steps(构建步骤)页面:

新构建步骤已添加

点击 + Add build step(+ 添加构建步骤)按钮,添加另一个 Gradle 构建步骤,任务为 assemble%env.FLAVOR%%env.VARIANT%。此步骤将为应用的给定版本和变体生成构建工件。

完成后,您的 Build Steps(构建步骤)页面应该会列出您创建的两个基于 Gradle 的构建步骤,以及将作为其中一部分运行的 Gradle 任务的快速摘要:

构建步骤已更新

接下来,您还需要做两件事:定义您使用过的两个环境变量的值,以及配置工件路径。

您已经知道如何为构建配置设置工件路径。对于此构建配置,将 Artifact paths(工件路径)字段设置为 app/build/outputs/apk/*/*/* => output,与上一个配置相同。

要为版本和变体字段设置矩阵值,请点击左侧导航窗格中的 Build Features(构建功能)。在 Build Features(构建功能)页面上,点击 + Add build feature(+ 添加构建功能)按钮,并在对话框的下拉菜单中搜索 Matrix Build(矩阵构建):

在 Build Features(构建功能)页面上搜索 Matrix Build(矩阵构建)

从下拉列表中选择 Matrix Build(矩阵构建)选项后,系统将要求您提供矩阵构建的形参及其值。提供形参名称 env.FLAVOR,值为 Free。添加另一个形参 env.VARIANT,它的两个值为 ReleaseDebug

配置矩阵构建

接下来,点击 Save(保存)按钮。至此,此管道上的矩阵构建设置完毕。您可以点击页面右上角的 Run(运行)按钮进行测试。

现在,您可以分别查看每次运行的结果,以及单独的构建工件和测试结果。

矩阵构建结果

您可以点击 Dependencies(依赖项)标签页,查看每个运行的构建运行详细信息:

查看个别构建运行详细信息

正如您之前看到的,您可以将每个条目作为独立的完整构建运行来探索。

打包和部署

Android CI/CD 管道的一个关键部分是向 Google Play 推送发行版二进制文件,以便向用户发布。您也可以使用 TeamCity 和 Gradle Play Publisher (GPP) 自动执行此操作。

在开始此流程之前,有一些先决条件:

1. 确保您已将 Android 项目的第一个 APK/AAB 手动上传到 Google Play 管理中心。

2. 您必须拥有有效的签名配置

3. 您需要在 Google Cloud Platform 上创建一个服务账号,以便能够使用 Google Play Developer API 并检索其 JSON 凭据文件。为此,请按照以下步骤操作,然后再进行下一步。

完成上述链接中的详细步骤后,您需要在 Android 项目中安装和配置 GPP。为此,请将以下代码行添加到应用级 build.gradle.kts 文件中的插件块中:

kt
id("com.github.triplet.play") version "3.9.1"

然后,在此文件的根级添加一个 play {} 块,内容如下:

kt
play {
    serviceAccountCredentials.set(file("play_config.json"))
    track.set("internal")
    releaseStatus.set(ReleaseStatus.DRAFT)
    defaultToAppBundles.set(true)
}

这会将 GPP 配置为使用名为 play_config.json 的文件中的服务账号凭据,在将二进制文件推送到 Play 管理中心时将轨道设置为 internal,并将发布状态设置为 DRAFT,并默认使用应用捆绑包代替 APK。

您的 Android 项目的必要配置步骤到此完成。在继续之前,将这些更改提交并推送到 GitHub 仓库。

现在,您将在 TeamCity 中创建一个新的构建配置,用于将二进制文件推送到 Google Play。按照与之前相同的步骤创建新的构建配置。将第一个构建步骤设置为使用 Gradle 作为运行程序,并将 bundleFreeRelease 作为要运行的 Gradle 任务:

构建步骤

在此构建配置中添加另一个步骤,但这次要选择 Command Line(命令行)作为构建运行程序:

配置新的命令行构建步骤

命令行运行程序的新构建步骤页面将会打开。您需要提供自定义脚本,用于签署应用捆绑包并将其发布到 Google Play。在 Custom script(自定义脚本)字段中输入以下代码:

# Create the keystore file from the environment variables
echo %env.ANDROID_KEYSTORE_FILE% > keystore.jks.b64
base64 -d -i keystore.jks.b64 > app/keystore.jks

# Sign the AAB using the keystore and credentials retrieved from the environment variables
jarsigner 
-keystore app/keystore.jks 
-storepass %env.KEYSTORE_STORE_PASSWORD% 
-keypass %env.KEYSTORE_KEY_PASSWORD% 
-signedjar release.aab 
app/build/outputs/bundle/freeRelease/app-free-release.aab 
%env.KEYSTORE_KEY_ALIAS%

# Create the GCP service account credentials file from the environment variables
echo %env.PLAY_CONFIG_JSON% > play_config.json.b64
base64 -d -i play_config.json.b64 > app/play_config.json

# Use GPP to publish the app bundle
./gradlew publishFreeBundle --artifact-dir release.aab

代码中的内联评论解释了每一行的作用。完成后,点击页面底部的 Save(保存)按钮:

配置您的命令行脚本

您还需要定义以下环境变量,以便为脚本提供正确的凭据来签署和发布应用:

bash
ANDROID_KEYSTORE_FILE
KEYSTORE_KEY_ALIAS
KEYSTORE_KEY_PASSWORD
KEYSTORE_STORE_PASSWORD
PLAY_CONFIG_JSON

点击左侧导航窗格中的 Parameters(形参),转到可以定义这些环境变量的页面。您会看到 TeamCity 已经在此页面上为您填充了所需变量的列表:

查看新识别的环境变量

对于 KEYSTORE_KEY_ALIASKEYSTORE_KEY_PASSWORDKEYSTORE_STORE_PASSWORD,请随意点击 Edit(编辑)并在相应的对话框中提供它们的值:

配置环境变量

对于 ANDROID_KEYSTORE_FILEPLAY_CONFIG_JSON,您首先需要使用 openssl 等工具将文件转换为 Base64,然后将 Base64 编码的内容粘贴到这些变量的值字段中。

这将设置管道来构建和发布应用程序的免费版本的签名发布版。您可以尝试点击页面右上角的 Run(运行)按钮,触发运行并查看运行情况。

运行成功后,您将在日志中看到 BUILD SUCCESSFUL 消息:

成功构建结果日志

以下是您的应用的最新版本,可在Google Play 管理中心的内部轨道上查看,并随时供您编辑和推广:

Play 管理中心内部测试页面

您会注意到,新版本的名称 (“2.0”) 与上一版本面向开发者的名称相同。这是因为没有在 GPP 配置中指定名称。您可以查看 GPP 文档,了解如何自行完成该操作。


最佳做法和提示

现在,您可以使用 TeamCity 为 Android 设置自己的管道,以下是您可以考虑实施的一些关键最佳做法,以确保您的管道高效快速:

1. 版本控制和版本管理做法:高效的 CI/CD 管道在很大程度上依赖于强大的版本控制系统 (VCS),如 Git。确保您的团队坚持明确的版本管理做法,并实施一致的分支策略(如功能分支)。例如,为不同的分支开发自定义的管道,确保不会在 WIP 代码上运行不必要的步骤。

2. 明确的通过/失败标准和阈值:明确定义什么是成功的构建和测试运行。这可能涉及为单元测试覆盖率设置阈值,为其他代码检查亮起绿灯等。您应该针对管道的每个阶段为 TeamCity 配置通过/失败标准,以确保构建的可靠性,并鼓励开发者编写更好的代码。

3. 利用 TeamCity 通知和警报:TeamCity 提供了详细的通知系统,可以帮助通知用户跨 Web 浏览器、电子邮件、Slack 和 IDE 的管道事件。确保为构建失败和关键测试失败设置警报,以便让开发团队及时了解情况并迅速解决问题。

4. 协作和反馈循环:高效的 CI/CD 管道可以促进开发团队内部的协作。您应该在 TeamCity 中使用构建管道可视化,为开发者提供整个构建和测试流程的清晰概况。您还可以直接在 TeamCity 内使用测试和构建调查来指派调查和协作处理调查,以了解构建或测试失败的原因。同时,鼓励团队成员审查构建失败和代码覆盖率报告,以确定需要改进的地方。这有助于培养代码质量和持续改进的文化。

5. 安全措施(代码签名和访问控制):确保为 TeamCity 实例配置适当的访问控制,限制只有需要访问信息的用户才能访问签名密钥等敏感信息。您应该考虑使用类似 HashiCorp Vault 的工具来管理和轮换您在构建过程中可能使用的所有敏感凭据。您可以在这里查看 TeamCity 的其他一些关键安全建议。


结论

在本文中,您了解了如何使用 JetBrains TeamCity 为 Android 开发项目构建和管理详细的 CI/CD 管道。您探索了 Android CI/CD 管道从代码迁出和版本控制集成到签名、部署和监控等各个关键阶段。您了解了 TeamCity 如何促进每个阶段并简化开发工作流。最后,您还将学习一些关键的最佳做法,以确保您的管道高效运行。

通过使用 TeamCity 来设置 Android 管道,您可以大大提高工作流效率。这直接带来更快的发布周期、更少的 bug,最终高效交付高质量的 Android 应用。因此,请迈出更简化开发流程的第一步,立即开始构建您的 CI/CD 管道吧!


本博文英文原作者:Olga Bedrina

TeamCity 相关阅读

关于 TeamCity

TeamCity 是一款强大的持续集成和部署服务器,面向以 DevOps 为中心的团队提供开箱即用的测试智能、构建问题的实时报告以及无与伦比的可扩展性。安装和部署 TeamCity,几分钟之内即可开始构建您的 DevOps 管道。TeamCity 提供本地部署和基于云的版本。

进一步了解 TeamCity

⏬ 戳「阅读原文」了解更多信息


本文分享自微信公众号 - JetBrains(JetBrainsChina)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

微软开源基于 Rust 的 OpenHCL 字节跳动商业化团队模型训练被“投毒”,内部人士称未影响豆包大模型 华为正式发布原生鸿蒙系统 OpenJDK 新提案:将 JDK 大小减少约 25% Node.js 23 正式发布,不再支持 32 位 Windows 系统 Linux 大规模移除疑似俄开发者,开源药丸? QUIC 在高速网络下不够快 RustDesk 远程桌面 Web 客户端 V2 预览 前端开发框架 Svelte 5 发布,历史上最重要的版本 开源日报 | 北大实习生攻击字节AI训练集群;Bitwarden进一步脱离开源;新一代MoE架构;给手机装Linux;英伟达真正的护城河是什么?
{{o.name}}
{{m.name}}

猜你喜欢

转载自my.oschina.net/u/5494143/blog/15096274