Spice - Kotlin依赖图谱分析库 - 2

122 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

优势与隐忧

优势

  • 允许多功能选择构建工具
  • 启用CI的构建/测试避免(不构建不受影响的内容)超出构建系统的能力
  • 通过验证和构建分离简化仓库布局和验证.
    • 通过符号分析"Strict" deps (只声明使用的)能够被极大地简化
    • 对图谱的完全了解有助于实现更好的自动化
    • 可以通过符号分析和模板化来管理资源的排序
  • 简化engBuild所做的变量"trimming"
  • 通过多构建系统和途径启用低成本实验.

隐忧

  • 从开发者中抽象构建系统
  • 对某些构建问题采取分类途径(TBD - 分类插件说明)
  • IDE的初始有限支持
    • 比如Android Studio理解gradle, 但Spice并不.
  • 基于维护的需要引入新的工具

现有技术

对已经尝试过的类似尝试和系统的讨论.

Dropbox

  • Android构建系统现代化
  • 注意(WIP):
    • 在没有看到drop box实现的情况下, 我们只能基于可怜的数据推测问题是啥.
    • “BMBF采用非常特殊的文件和目录结构.”
    • “ BMBF不兼容于Gradle的增量构建因为build.gradle文件在每一次开发者构建应用的时候会被重新创建”
      • 构建文件应该只能在Workspace刷新或者从master或者模块增量中拉取时被构建.
      • 选项1: 后台线程只能用于理解Workspace中的变更并且只能按需生成gradle文件.
      • 选项2: 构建gradle插件, 它会读取spice文件并且配置图谱.
    • “对于在公司超过6个月的工程师, 如果依然不知道如何使用BMBF创建新的模块, 这是很不常见的. “
      • 应该基于工具而非定制添加新的模块.
        • 创建模块文件和生成构建系统配置的简单脚本会有很大帮助.
    • “BMBF很固执己见, 它使得向未构建进BMBF的Gradle脚本中添加函数很困难.”
      • 在疏忽仓库维护者的情况下开发者添加定制构建脚本是不理想的, 因为它增加了大规模的支持负担和成本.
      • 使元数据开放并尽可能地简单
      • 编码扩展点 (比如代码生成扩展, 像Anvil或者proto, 分析扩展等.)
        • 需要考虑的用例:
          • Anvil
          • 自定义源定位, 比如SQLDelight
          • 额外的元数据, 比如Android

Cocoapods

  • 它是如何关联的?
    • 稳定的元数据规范定义了依赖图谱和模块语义
    • Square的iOS bazel迁移成功的很大一部分是cocopods提供了这么一个稳定的迁移工具基础.
  • 我们只用Cocoapods可以吗?
    • iOS特定的
    • 元数据语言用运行时提供了条件语句和一些命令特性.
    • 用声明式内容混合了构建和打包相关的元数据.

Maven

  • 它是如何关联的?
    • 尽管构建系统就其本身而言, 它既是构建系统, 也是元数据系统.
    • Maven对于构建元数据确实有正确的想法.
    • 理论上POM可以作为构建元数据结构来服务, 但的确有一些问题:
      • xml是人类不可读的
      • POM有可扩展的混合, 包括纯构建元数据和额外的构建插件配置
  • 由于Maven元数据的流行, 向maven依赖的直接映射将会十分重要, 尤其是在JVM语言构建空间(比如gradle和bazel)对于外部(非项目)依赖
  • TBD细节: 我们喜欢什么, 我们如何必须区分, 等等.

例子

Spice yml 工作空间例子

tools:
- name: "dagger"
  any: ["kotlin", "java"]
- name: "anvil"
  all: ["kotlin", "dagger"]
- name: "android"
  all: ["kotlin", "java"]
- name: "kotlin"
- name: "sqldelight"
  all: ["kotlin"]
  not: ["sqldelight_legacy"]
- name: "test"

external:
- name: "maven",
  urls: [...repo urls..],
  artifacts: { com.foo.thing : "0.4.1" }

Spice yml 模块例子

shared:
srcs: ["src/main"]
deps: ["/other:module", "/maven/com.foo.thing"]
tools: ["android", "hephaestus"]

variant:
- name: "debug"
  srcs: ["src/debug"]
  deps: ["/debug/module"]
- name: "v2"
  overlay: ["debug"]
  deps: ["/other:module"]
- name: "v1"
  overlay: ["debug"]

Gradle Kotlin 模版例子

注意: 这是一个比较早期的例子, 源于设计的早期版本. 用作伪代码进行解释.

pluginNames = mapOf(
  "anvil" to "com.squareup.anvil"
)
Cabinet(".").render { spice ->
  spice.workspace.apply {
    Gradle.Workspace { 
      block("plugins") {
        tools.forEach { p ->
          call("invoke", pluginNames[p.name], call("version", p.version)
        }
      }
      block("repositories") {
        external.urls.forEach{ u -> 
          block("maven") { call("url", u.url) }
        }
      }
    }
  }
  spice.modules.forEach { module ->
    Gradle.Build(module.path) {
     module.tools.forEach { p -> 
        id pluginNames[p.name]
     }
     block("android") { 
       module.variants.forEach { v -> 
         block(v.name) { ... variant specific attributes ... }
       }
     }
     block("dependencies") {
       module.shared.deps { d -> 
           when {
             d.external -> "main" implementation d.coordinates
             else -> "main" implementation call("project", d.coordinates)
           }
      }
      module.variants.forEach { v -> 
        when {
          // TODO: fix this
          d.external -> v.name (implementation) (d.coordinates)
          else -> v.name (implementation) call("project", d.coordinates)
        }
      }
    }
  }

存储格式选项

  • Protocol buffers
    • 用起来有点古怪, 但有成熟的工具支持
    • 优势: 支持定义类型, 有ide的支持
    • 隐忧: 不容易验证
      • 验证逻辑可以通过custom_options来完成
  • Yaml
    • 通常且已经用于配置
    • 优势: 支持定义类型, 有ide的支持
    • 隐忧: 不容易验证
  • Json
    • 众所周知
    • 优势: 成熟的工具支持
    • 隐忧: 不容易验证
  • Xml
    • 哈哈哈. 哦等一下你是认真的. 让我笑得更厉害点.
    • 隐忧: 冗余
  • Custom
    • 语法不熟悉
  • 子集Gradle
    • 隐忧: 太松散了, 期待Kotlin或者Groovy.
  • Kotlin DSL
    • 编译类型安全, 语言众所周知
    • 优势: 能够将验证构建进DSL
    • 隐忧: 需要禁止条件语句和其它的语言特性, 比如变量, 函数定义, 类, 等等.
  • Starlark子集
    • 隐忧: 可能执行得比较慢.
    • 隐忧: 通过添加函数和条件语句增加了元数据的复杂性

术语

  • Project (又名 node) - 配置单元, 通过依赖关于到其它的Project, 并应用到该Project的工具.
    • 比如: gradle project, bazel target
  • Dependency (又名 edge) - Project之间的关系, 形成了有向无环图
  • Tool - 标签, 引用了一些工具, 这些工具将会应用到不同场景下的node.
    • Tool在元数据中进行抽象, 并恰当地应用于任何上下文.
    • 比如“java”在构建系统中意味着java编译, 但可能在检索上下文中被忽略.
    • 另一个看待Tools的方式是作为配置信号, 基础传输单位, 因为通常情况下这引用了要么是工具链要么是专门配置的工具链, Tool就是这个术语.

进化中的设计决策

暂定的设计决策可能会发生改变, 但应该被记录, 所以我们能够持续追踪.

  • 依赖排除和deps门诊
    • Bazel风格的workspace层次的规范
      • 使用maven/gradle声明站点需要是导入程序的一项功能
    • 工作空间需要添加/删除/替换语法勾子来进行deps门诊
    • 声明站点的deps门诊需要使用封装了“reformed”节点的新项目来完成.