函式庫作者指南

語言

這份文件針對想要將自己的作品發布為其他程式可以依賴的函式庫的開發人員。這份文件逐步說明在發布開源函式庫之前應該回答的主要問題,並說明典型的開發環境是什麼樣子。

在這裡給出建議的範例程式庫,可於 https://github.com/scalacenter/library-example 取得。為了簡潔起見,此範例使用常見的技術,例如 GitHub、Travis CI 和 sbt,但會提到其他技術,而調整此文件內容以適用於這些技術應當很簡單。

選擇開源授權

第一步包括選擇開源授權,指定其他人可以如何重新使用程式庫。您可以在 opensource.org 網站上瀏覽現有的開源授權。如果您不知道要選擇哪一個,我們建議使用 Apache License 2.0,它允許使用者使用(包括商業用途)、分享、修改和重新散布(包括在不同條款下)您的作品,條件是保留授權和著作權公告。有記錄以來,Scala 本身已取得 Apache 2.0 授權。

選擇授權後,在專案的根目錄中建立一個包含授權內容或連結的 LICENSE 檔案,套用到您的專案。此檔案通常會指出誰擁有著作權。在我們的 LICENSE 檔案 範例中,我們寫道所有貢獻者(根據 Git 記錄)擁有著作權。

主機原始碼

我們建議透過在公開的 Git 主機網站(例如 GitHubBitbucketGitLab)上主機來分享程式庫的原始碼。在我們的範例中,我們使用 GitHub。

您的專案應包含一個 README 檔案,其中包含程式庫功能的說明和一些文件(或文件連結)。

您應注意僅將原始碼檔案置於版本控制之下。例如,由建置系統產生的成品不應進行版本控制。您可以透過將此類檔案新增到 .gitignore 檔案中,指示 Git 忽略這些檔案。

如果您使用 sbt,請確定您的儲存庫具有 project/build.properties 檔案,指出要使用的 sbt 版本,如此一來,處理您的儲存庫的人員 (或工具) 將自動使用正確的 sbt 版本。

設定持續整合

設定持續整合 (CI) 伺服器的首要原因,是要在拉取要求上系統化執行測試。免費提供給開源專案的 CI 伺服器範例包括 GitHub ActionsTravis CIDroneAppVeyor

我們的範例使用 GitHub Actions。此功能預設在 GitHub 儲存庫中啟用。您可以在儲存庫的 [設定] 標籤中的 [動作] 區段中驗證是否如此。如果已勾選 [停用所有動作],則表示動作未啟用,您可以透過選取 [允許所有動作]、[僅允許本機動作] 或 [允許選取動作] 來啟用動作。

啟用動作後,您可以建立 [工作流程定義檔案]。**工作流程** 是自動化程序,由一個或多個工作組成。**工作** 是在同一個執行器上執行的連續步驟組。**步驟** 是可以執行指令的個別工作;步驟可以是 [動作] 或 shell 指令。**動作** 是工作流程中最小的建構區塊,可以重複使用社群動作或定義新的動作。

若要建立工作流程,請在儲存庫中建立 .github/workflows/ 目錄中的 [yaml] 檔案,例如 .github/workflows/ci.yml,內容如下

name: Continuous integration
on: push

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3 # Retrieve the content of the repository
      - uses: actions/setup-java@v3 # Set up a jdk
        with:
          distribution: temurin
          java-version: 8
          cache: sbt # Cache the artifacts downloaded by sbt accross CI runs
      - name: unit tests # Custom action consisting of a shell command
        run: sbt +test

此工作流程稱為持續整合,每次將一個或多個提交推送到儲存庫時,它就會執行。它只包含一個稱為ci 的工作,它將在 Ubuntu 執行器上執行,並由三個動作組成。動作 setup-java 安裝 JDK 並快取 sbt 下載的程式庫相依性,以便在每次執行 CI 時不會再次下載。

然後,工作執行 sbt +test,它載入 project/build.properties 中指定的 sbt 版本,並使用 build.sbt 檔案中定義的 Scala 版本執行專案測試。

上述工作流程將在任何推送到儲存庫的任何分支執行。您可以指定分支或新增更多觸發器,例如拉取要求、版本、標籤或排程。有關工作流程觸發器的更多資訊,請參閱 這裡,而 setup-java 動作則 在此儲存庫中 託管。

供參考,以下是我們的完整 工作流程範例檔案

發布版本

大多數建置工具透過在公開儲存庫中查詢來解析第三方相依性,例如 Maven Central。這些儲存庫會託管程式庫二進位檔以及其他資訊,例如程式庫作者、開源授權和程式庫本身的相依性。程式庫的每個版本都由 groupIdartifactIdversion 號碼識別。例如,考慮以下相依性(以 sbt 語法撰寫)

"org.slf4j" % "slf4j-simple" % "1.7.25"

它的 groupIdorg.slf4j,它的 artifactIdslf4j-simple,它的 version1.7.25

在此文件中,我們展示如何發布 Maven Central 儲存庫。此程序需要有 Sonatype 帳戶和 PGP 金鑰對來簽署二進位檔。

建立 Sonatype 帳戶和專案

依照 OSSRH 指南 上的說明建立新的 Sonatype 帳戶(除非您已經有帳戶),並 建立新的專案票證。後續步驟中,您將定義要發佈的 groupId。您可以使用您已經擁有的網域名稱,否則常見的做法是使用 io.github.(username)(其中 (username) 會替換成您的 GitHub 使用者名稱)。

此步驟僅需針對您想要擁有的每個 groupId 執行一次。

建立 PGP 金鑰對

Sonatype 要求 您使用 PGP 簽署已發佈的檔案。依照 這裡 的說明產生金鑰對,並將您的公開金鑰分發到金鑰伺服器。

此步驟僅需針對每個人執行一次。

設定您的專案

如果您使用 sbt,我們建議使用 sbt-sonatypesbt-pgp 外掛程式來發佈您的成品。將下列依賴項新增到您的 project/plugins.sbt 檔案

addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")

並確保您的建置符合 Sonatype 需求,方法是定義下列設定

// used as `artifactId`
name := "library-example"

// used as `groupId`
organization := "ch.epfl.scala"

// open source licenses that apply to the project
licenses := Seq("APL2" -> url("https://www.apache.org/licenses/LICENSE-2.0.txt"))

description := "A library that does nothing useful"

import xerial.sbt.Sonatype._
sonatypeProjectHosting := Some(GitHubHosting("scalacenter", "library-example", "[email protected]"))

// publish to the sonatype repository
publishTo := sonatypePublishToBundle.value

將您的 Sonatype 認證資料放入 $HOME/.sbt/1.0/sonatype.sbt 檔案中

credentials += Credentials("Sonatype Nexus Repository Manager",
        "oss.sonatype.org",
        "(Sonatype user name)",
        "(Sonatype password)")

(將您的實際使用者名稱和密碼放入 (Sonatype user name)(Sonatype password) 的位置)

切勿將此檔案提交到版本控制中。

最後,我們建議使用 sbt-dynver 外掛程式來設定您的發佈版本號碼。將下列依賴項新增到您的 project/plugins.sbt 檔案

addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.0.1")

並確保您的建置沒有定義 version 設定。

剪輯版本

有了這個設定,剪輯版本的流程如下。

建立一個 Git 標籤,其名稱開頭為小寫 v,後面接版本號碼

$ git tag v0.1.0

這個標籤由 sbt-dynver 用來計算版本的版本 (0.1.0,在此範例中)。

使用 publishSigned sbt 任務將您的成品部署到中央儲存庫

$ sbt publishSigned

sbt-sonatype 會封裝您的專案並要求您的 PGP 密碼以使用您的 PGP 金鑰簽署檔案。然後它會使用您的帳戶憑證將檔案上傳到 Sonatype。當任務完成時,您可以在 Nexus Repository Manager 中檢查成品(在側邊選單中的「暫存儲存庫」中 − 如果您看不到它,請確認您已登入)。

最後,使用 sonatypeRelease sbt 任務執行版本

$ sbt sonatypeRelease

設定持續發佈

上述的版本流程有一些缺點

  • 它需要執行三個指令,
  • 它無法保證程式庫在發佈時處於穩定狀態(亦即,有些測試可能會失敗),
  • 如果您在團隊中工作,每個貢獻者都必須設定自己的 PGP 金鑰對,並且必須擁有 Sonatype 憑證才能存取專案的 groupId

持續發佈透過將發佈流程委派給 CI 伺服器來解決這些問題。它的運作方式如下:任何具有寫入儲存庫權限的貢獻者都可以透過推播 Git 標籤來剪輯版本,CI 伺服器會先檢查測試是否通過,然後執行發佈指令。

我們透過在檔案 project/plugins.sbt 中將外掛 sbt-pgpsbt-sonatypesbt-dynver 替換為 sbt-ci-release 來達成此目的

- addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
- addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21")
- addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.0.1")
+ addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12")

其餘章節顯示如何設定 GitHub Actions 以持續在 Sonatype 上發佈。您可以在 sbt-ci-release 外掛程式文件找到 Travis CI 的說明。

設定 CI 伺服器

您必須向 CI 伺服器提供您的 Sonatype 帳戶憑證,以及您的 PGP 金鑰對。幸運的是,您可以使用 CI 伺服器的秘密管理系統安全地提供這些資訊。

匯出您的 Sonatype 帳戶憑證

為您的 Sonatype 帳戶憑證建立兩個 GitHub 加密秘密SONATYPE_USERNAMESONATYPE_PASSWORD。為此,請前往儲存庫的「設定」標籤,然後在左面板中選擇「秘密」。然後,您可以使用按鈕「新的儲存庫秘密」開啟秘密建立功能表,您將在其中輸入秘密的名稱及其內容。

儲存庫秘密讓我們可以安全地儲存機密資訊,並將其公開給 Actions 工作流程,而無需承擔將其提交至 git 歷程的風險。

匯出您的 PGP 金鑰對

若要匯出您的 PGP 金鑰對,您首先需要知道其識別碼。使用下列指令列出您的 PGP 金鑰

$ gpg --list-secret-keys
/home/julien/.gnupg/secring.gpg
-------------------------------
sec   2048R/BE614499 2016-08-12
uid                  Julien Richard-Foy <[email protected]>

在我的案例中,我有一個金鑰對,其 ID 為 BE614499

然後

  1. 建立一個新的秘密,其中包含名為 PGP_PASSPHRASE 的 PGP 金鑰的密碼。
  2. 建立一個新的秘密,其中包含名為 PGP_SECRET 的私鑰的 base64 編碼秘密。編碼秘密可透過執行下列指令取得
    # macOS
    gpg --armor --export-secret-keys $LONG_ID | base64
    # Ubuntu (assuming GNU base64)
    gpg --armor --export-secret-keys $LONG_ID | base64 -w0
    # Arch
    gpg --armor --export-secret-keys $LONG_ID | base64 | sed -z 's;\n;;g'
    # FreeBSD (assuming BSD base64)
    gpg --armor --export-secret-keys $LONG_ID | base64
    # Windows
    gpg --armor --export-secret-keys %LONG_ID% | openssl base64
    
  3. 將您的公開金鑰簽章發佈至公開伺服器,例如 http://keyserver.ubuntu.com:11371。您可以透過執行下列指令取得簽章
    # macOS and linux
    gpg --armor --export $LONG_ID
    # Windows
    gpg --armor --export %LONG_ID%
    

    (將 (金鑰 ID) 替換為您的金鑰 ID)

從 CI 伺服器發布

在 GitHub Actions 上,您可以定義一個工作流程,在推送以「v」開頭的標籤時發布程式庫

# .github/workflows/publish.yml
name: Continuous publication
on:
  push:
    tags: [v*]

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0 # fetch all tags, required to compute the release version
      - uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 8
          cache: sbt
      - run: sbt ci-release
        env:
          PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
          PGP_SECRET: ${{ secrets.PGP_SECRET }}
          SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
          SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}

env 陳述式會透過環境變數將您先前定義的機密公開給發布程序。

剪輯版本

只要推送 Git 標籤

$ git tag v0.2.0
$ git push origin v0.2.0

這會觸發工作流程,最終會呼叫 sbt ci-release,它會執行 publishSigned,後接 sonatypeRelease

跨平台發布

如果您已撰寫程式庫,您可能會希望它可以在多個 Scala 主要版本(例如 2.12.x、2.13.x、3.x 等)中使用。

build.sbt 檔案中,在 crossScalaVersions 設定中定義您要支援的版本

crossScalaVersions := Seq("3.3.0", "2.13.12", "2.12.18")
scalaVersion := crossScalaVersions.value.head

第二行讓 sbt 預設使用 crossScalaVersions 的第一個 Scala 版本。CI 作業會使用您的建置定義的所有 Scala 版本。

發布線上文件

文件的重要屬性是範例程式碼應該可以編譯並按照呈現的方式運作。有各種方法可以確保此屬性成立。一種方法是由 mdoc 支援,實際上是評估範例程式碼並將其評估結果寫入產生的文件中。另一種方法是嵌入來自實際模組或範例的原始碼片段。

sbt-site 外掛程式可以協助您整理、建置和預覽您的文件。它與其他 sbt 外掛程式整合良好,這些外掛程式用於產生文件內容或將產生的文件發布到網路伺服器。

最後,一個用於線上發佈文件說明的簡單解決方案是使用 GitHub Pages 服務,這項服務會自動提供給每個 GitHub 儲存庫。 sbt-ghpages 外掛程式可以自動上傳 sbt-site 至 GitHub Pages。

建立文件說明網站

在此範例中,我們選擇使用 Paradox,因為它在 JVM 上執行,因此不需要在系統上設定另一個 VM(與大多數其他文件說明產生器形成對比,這些產生器是基於 Ruby、Node.js 或 Python)。

若要安裝 Paradox 和 sbt-site,請將下列程式碼行加入您的 project/plugins.sbt 檔案

addSbtPlugin("com.github.sbt" % "sbt-site-paradox" % "1.5.0")

然後將下列設定加入您的 build.sbt 檔案

enablePlugins(ParadoxSitePlugin, SitePreviewPlugin)
Paradox / sourceDirectory := sourceDirectory.value / "documentation"

ParadoxSitePlugin 提供一個 makeSite 任務,它會使用 Paradox 產生一個網站,而 SitePreviewPlugin 在處理網站內容時提供方便的任務,以在您的瀏覽器中預覽結果。第二行是選用的,它定義網站原始檔的位置。在我們的案例中,位於 src/documentation

src/documentation/index.md 檔案中加入您的文件說明進入點。典型的文件說明進入點會使用函式庫名稱作為標題,顯示簡短的句子描述函式庫的用途,以及將函式庫加入建置定義的程式碼片段

# Library Example

A library that does nothing.

## Setup

Add the following dependency to your `build.sbt` file:

@@@vars
~~~ scala
libraryDependencies += "ch.epfl.scala" %% "library-example" % "$project.version$"
~~~
@@@

@@@ index
* [Getting Started](getting-started.md)
* [Reference](reference.md)
@@@

請注意,在我們的案例中,我們依賴變數替換機制,在文件檔中注入正確的版本編號,這樣我們在每次發佈新版本時,就不必總是更新文件檔的該部分。

我們的範例也包含一個 @@@index 指令,定義文件檔內容如何組織。在我們的案例中,文件檔包含兩頁,第一頁提供一個快速教學課程,讓您熟悉程式庫,第二頁提供更詳細的資訊。

sbt-site 外掛提供一個方便的 previewAuto 任務,在本地端提供產生的文件檔,這樣您就可以看到它的外觀,並在您編輯它時重新產生文件檔

sbt:library-example> previewAuto
Embedded server listening at
  https://127.0.0.1:4000
Press any key to stop.

瀏覽 https://127.0.0.1:4000 URL 來查看結果

包含程式碼範例

此部分顯示兩種方法,以確保包含在文件檔中的程式碼範例會編譯並執行,就像它們顯示的那樣。

使用 Markdown 預處理器

一種方法是使用 Markdown 預處理器,例如 mdoc。這些工具會讀取您的 Markdown 原始檔,搜尋程式碼區塊,評估它們(如果它們無法編譯,則會擲回錯誤),並產生一個 Markdown 檔副本,其中程式碼區塊已更新,以包含評估 Scala 表達式的結果。

嵌入程式碼片段

另一種方法是嵌入 Scala 原始檔片段,這些片段是編譯組建的一部分。例如,在檔案 src/test/ch/epfl/scala/Usage.scala 中給定以下測試

package ch.epfl.scala

import scalaprops.{Property, Scalaprops}

object Usage extends Scalaprops {

  val testDoNothing =
// #do-nothing
    Property.forAll { x: Int =>
      Example.doNothing(x) == x
    }
// #do-nothing

}
package ch.epfl.scala

import scalaprops.{Property, Scalaprops}

object Usage extends Scalaprops:

  val testDoNothing =
// #do-nothing
    Property.forAll: (x: Int) =>
      Example.doNothing(x) == x
// #do-nothing

end Usage

您可以使用 @@snip Paradox 指令,將被 #do-nothing 識別符號包圍的片段嵌入,如 src/documentation/reference.md 檔案所示

# Reference

The `doNothing` function takes anything as parameter and returns it unchanged:

@@snip [Usage.scala]($root$/src/test/scala/ch/epfl/scala/Usage.scala) { #do-nothing }

產生的文件如下所示

包含 API 文件

從您的文件網站連結到 API 文件 (Scaladoc) 也可能很有用。

這可透過將下列行新增到您的 build.sbt 來達成

enablePlugins(SiteScaladocPlugin)
SiteScaladoc / siteSubdirName := "api"
paradoxProperties += ("scaladoc.base_url" -> "api")

SiteScaladocPluginsbt-site 提供,並將 API 文件包含到產生的網站。第二行定義 API 文件應發布在 /api 基本 URL,第三行讓 Paradox 可使用此資訊。

然後,您可以使用 @scaladoc Paradox 指令,以包含連結到您的程式庫中特定符號的 API 文件

Browse the @scaladoc[API documentation](ch.epfl.scala.Example$) for more information.

@scaladoc 指令將產生連結到 /api/ch/epfl/scala/Example$.html 頁面。

發布文件

sbt-ghpages 外掛程式新增到您的 project/plugins.sbt

addSbtPlugin("com.github.sbt" % "sbt-ghpages" % "0.8.0")

並將下列組態新增到您的 build.sbt

enablePlugins(GhpagesPlugin)
git.remoteRepo := sonatypeProjectHosting.value.get.scmUrl

在您的專案儲存庫中建立 gh-pages 分支,如 sbt-ghpages 文件 中所述。

最後,透過執行 ghpagesPushSite sbt 任務來發布您的網站

sbt:library-example> ghpagesPushSite
[info] Cloning into '.'...
[info] [gh-pages 2e7f426] updated site
[info]  83 files changed, 8059 insertions(+)
[info]  create mode 100644 .nojekyll
[info]  create mode 100644 api/ch/epfl/index.html
…
[info] To [email protected]:scalacenter/library-example.git
[info]    2d62539..2e7f426  gh-pages -> gh-pages
[success] Total time: 9 s, completed Jan 22, 2019 10:55:15 AM

您的網站應會上線於 https://(organization).github.io/(project)。在我們的案例中,您可瀏覽 https://scalacenter.github.io/library-example/

持續出版

您可以延伸 .github/workflows/publish.yml 自動將文件發布到 GitHub 頁面。為此,請新增另一個工作

# .github/workflows/publish.yml
name: Continuous publication

jobs:
  release: # The release job is not changed, you can find it above
  publishSite:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 8
          cache: sbt
      - name: Generate site
        run: sbt makeSite
      - uses: JamesIves/[email protected]
        with:
          branch: gh-pages
          folder: target/site

和往常一樣,透過推送 Git 標籤來建立發行版本。CI 伺服器會執行測試、發布二進位檔並更新線上文件。

歡迎貢獻者

本節提供建議,說明如何讓他人更容易為您的專案做出貢獻。

CONTRIBUTING.md

CONTRIBUTING.md 檔案新增到您的儲存庫,並回答下列問題:如何建置專案?有哪些程式設計慣例需要遵循?測試在哪裡?如何執行測試?

供您參考,您可以閱讀我們的 CONTRIBUTING.md 檔案 最小範例。

問題標籤

我們建議您標記專案問題,讓潛在貢獻者可以快速查看問題的範圍(例如,「文件」、「核心」,…),其難度等級(例如,「適合新手的第一個問題」、「進階」,…),或其優先順序(例如,「阻擋程式」、「很樂意執行」,…)。

程式碼格式

檢閱大量的拉取要求,其中實質變更被程式碼樣式變更稀釋,可能會令人沮喪。您可以透過使用程式碼格式化程式,強制所有貢獻者遵循特定的程式碼樣式,來避免這個問題。

例如,若要使用 scalafmt,請將下列程式碼新增到您的 project/plugins.sbt 檔案

addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2")

CONTRIBUTING.md 檔案中,提到您使用該程式碼格式化程式,並鼓勵使用者使用編輯器的「儲存時格式化」功能。

在您的 .github/workflows/ci.yml 檔案中,新增檢查程式碼是否已正確格式化的步驟

# .github/workflows/ci.yml
# The three periods `...` indicate the parts of file that do not change
# from the snippets above and they are omitted for brevity
jobs:
  ci:
    # ...
    steps:
      # ...
      - name: Code style
        run: sbt scalafmtCheck

演進

從使用者的角度來看,升級到新版本的函式庫應為順暢的過程。甚至可能是一個「非事件」。

重大變更和遷移步驟應詳加記錄,我們建議遵循 語意版本控制 政策。

MiMa 工具可協助您檢查是否違反此版本控制政策。將 sbt-mima-plugin 新增到您的建置,如下所示,在您的 project/plugins.sbt 檔案中

addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.2")

build.sbt 中,如下所示進行設定

mimaPreviousArtifacts := previousStableVersion.value.map(organization.value %% name.value % _).toSet

最後,將下列步驟新增到 .github/workflows/ci.yml 檔案中, Continuous integration 工作流程的 ci 作業

# .github/workflows/ci.yml
# The three periods `...` indicate the parts of file that do not change
# from the snippets above and they are omitted for brevity

# ...
jobs:
  ci:
    # ...
    steps:
      # ...
      - name: Binary compatibility
        run: sbt mimaReportBinaryIssues

這將檢查 pull request 是否會造成與前一個穩定版本二進位不相容的變更。

我們建議使用下列 Git 工作流程: main 分支總是接收下一個主要版本的 pull request(因此,透過將 mimaPreviousArtifacts 值設定為 Set.empty 來停用二進位相容性檢查),且每個主要版本 N 都有一個對應的 N.x 分支(例如, 1.x2.x 等),其中啟用了二進位相容性檢查。

此頁面的貢獻者