Shac (可擴充的密封分析和檢查) 是一種統一且符合人體工學的工具和架構,可用於編寫及執行靜態分析檢查。您可以在 shac 說明文件中找到該工具的來源。Shac 檢查項目會以 Starlark 編寫。
設定
Shac 指令碼實作項目位於 Fuchsia 的 //scripts/shac
目錄中。
- shac 檢查會以 Starlark 函式實作,該函式會使用 ctx 引數。使用這個 ctx 引數存取 SHAC 標準程式庫。
- 如果檢查項目是特定語言,則應放入特定語言檔案 (例如
rust.star
、go.star
、fidl.star
)。如果是特定語言,但沒有language.star
檔案,請建立一個。如果是一般型別,請使用title.star
(其中 title 是檢查函式的名稱)。
簡單範例
以下範例是所有檔案的靜態分析器,會在存在「http://」字串的變更中建立非封鎖的 gerrit 警告註解,指示使用者改用「https://」。
def http_links(ctx):
for path, meta in ctx.scm.affected_files().items():
for num, line in meta.new_lines():
matches = ctx.re.allmatches(r"(http://)\w+", line)
if not matches:
continue
for match in matches:
ctx.emit.finding(
message = "Avoid http:// links, prefer https://",
# Change to "error" if the check should block presubmit.
level = "warning",
filepath = path,
line = num,
col = match.offset + 1,
end_col = match.offset + 1 + len(match.groups[1]),
replacements = ["https://"],
)
進一步瞭解 shac 如何實作 emit.findings。
請注意,Shac 不會自動偵測檢查項目。為了執行檢查,必須將檢查函式傳遞至 //scripts/shac/main.star
中的 shac.register_check()
:
load("./http_links.star", "http_links") # NEW
...
def register_all_checks():
...
shac.register_check(http_links) # NEW
...
在已包含其他檢查項目的檔案中實作新檢查項目時,您可能可以在該檔案中註冊新檢查項目。例如,//scripts/shac/fidl.star
有一個從 //scripts/shac/main.star
呼叫的 register_fidl_checks()
函式。將新的 FIDL 檢查新增至 fidl.star
,並在同一個檔案的 register_fidl_checks()
函式中註冊這些檢查。
進階範例
如果有現有的工具可執行檢查,或是檢查的邏輯較為複雜 (例如不只是子字串搜尋),使用子程序就很實用。Starlark 會刻意限制功能,鼓勵您在獨立工具中編寫複雜的業務邏輯,並搭配專屬的單元測試。
以下是 JSON 格式化工具在個別 Python 指令碼中實作,並以子程序執行的範例。
檢查作業不會重寫格式不正確的檔案,而是會計算格式化的內容,並將其傳遞至 ctx.emit.finding()
函式的 replacements
引數。所有格式檢查都必須以這種方式實作,原因如下:
- 檢查執行的子程序不得寫入檢查目錄中的檔案。這可避免工具發生錯誤,導致發生意料之外的變更,並確保同時執行多項檢查作業時不會發生競合情況。(請注意,檔案系統沙箱功能僅適用於 Linux)。
- Shac 的設計可輕鬆整合其他需要向使用者提出變更 (例如在 Gerrit 中) 的自動化功能,而非自動套用變更,因此為了讓這些用途運作,差異必須傳遞至 shac,而非由子程序套用。
import json
import sys
def main():
# Accepts one positional argument referring to the file to format.
path = sys.args[1]
with open(path) as f:
original = f.read()
# Always use 2-space indents and a trailing blank line.
formatted = json.dumps(json.loads(original), indent=2) + "\n"
if formatted == original:
sys.exit(0)
else:
print(json.dumps(doc, indent=2) + "\n")
sys.exit(1)
if __name__ == "__main__":
main()
load("./common.star", "FORMATTER_MSG", "cipd_platform_name", "get_fuchsia_dir", "os_exec")
def json_format(ctx):
# Launch processes in parallel.
procs = {}
for f in ctx.scm.affected_files():
if not f.endswith(".json"):
continue
# Call fuchsia-specific `os_exec` function instead of
# `ctx.os.exec()` to ensure proper executable resolution.
# `os_exec` starts the subprocess but does not block.
procs[f] = os_exec(ctx, [
"%s/prebuilt/third_party/python3/%s/bin/python3" % (
get_fuchsia_dir(ctx),
cipd_platform_name(ctx),
),
"scripts/shac/json_format.py",
f,
])
for f, proc in procs.items():
# wait() blocks until the process completes.
res = proc.wait()
if proc.retcode != 0:
ctx.emit.finding(
level = "error",
filepath = f,
# FORMATTER_MSG is the standard message for formatters
# in fuchsia.git.
message = FORMATTER_MSG,
# json_format.py prints the formatted file contents to stdout.
# Passing it to `replacements` is necessary for shac to know
# how to apply the fix.
replacements = [res.stdout],
)
# TODO: call this somewhere
shac.register_check(shac.check(
json_format,
# Mark the check as a formatter. Only checks with `formatter = True`
# get run by `fx format-code`.
formatter = True,
))
效能最佳化
部分格式化工具內建支援功能,可一次驗證多個檔案的格式,這類作業通常會在內部並行處理,因此比啟動個別子程序來檢查每個檔案更快。在這種情況下,您可以在「檢查」模式下針對所有檔案執行格式化器一次,取得格式不正確的檔案清單,然後只針對格式不正確的檔案進行迴迭,以取得格式化結果 (而非對所有檔案進行迴迭)。
範例:針對 rustfmt,請先執行 rustfmt --check --files-with-diff
<all rust files>
取得格式不正確的檔案清單,然後分別對每個檔案執行 rustfmt
,取得格式化的結果。
如果格式化工具沒有模擬模式,無法將格式化結果列印到 stdout
:格式化工具子程序將無法寫入檢查點。不過,部分格式化工具會無條件寫入檔案。在這種情況下,您需要將每個檔案複製到子程序可寫入的 tempdir,並讓子程序格式化臨時檔案,然後回報檔案內容。如需範例,請參閱 buildifier。
根據預設,如果子程序產生非零的傳回碼,os_exec
就會擲回無法復原的錯誤。如果預期傳回碼非零,您可以使用 ok_retcodes 參數,例如,如果格式化工具在檔案未格式化時產生 1 的傳回碼,則 ok_retcodes = [0, 1]
可能會適合。
在本機執行檢查
在本機檢查開發期間,建議您直接透過 fx host-tool shac check <file>
執行 SHAC 來測試檢查。我們來建立情境,以便測試上述的 http_links
檢查:
- 找出目前違反檢查的檔案,或建立新的檔案 (如果不存在),例如:
echo "http://example.com" > temp.txt
fx host-tool shac check --only http_links temp.txt
- 這應該會失敗,並顯示檔案內容,其中「http://」會以醒目顯示的方式顯示
--only
會讓 shac 只執行 http_links 檢查,而排除其他檢查,因為在這個例子中,我們只在意測試 http_links,而不會在意其他檢查的結果
fx host-tool shac fix --only http_links temp.txt
應將 http:// 改為 https://fx host-tool shac check --only http_links temp.txt
應該會通過fx host-tool shac check --only http_links --all
- 在樹狀結構中的所有檔案上執行 (除了 Git 忽略的檔案或
//shac.textproto
中忽略的檔案),而非只在變更的檔案上執行 - 如果這項操作失敗並出現錯誤,您必須在發生錯誤的檔案中修正這些錯誤,可以是同一個提交或在個別提交中修正 (如果有超過 10 個檔案需要修正,建議採用這種方式),然後再提交檢查。
- 或者,將檢查設為非阻斷式,修正錯誤,然後將其切換為阻斷式
- 如果檢查作業會發出警告,請記下警告數量。如果數量非常龐大 (超過 100 個),Gerrit 會產生許多雜訊留言,並可能對其他貢獻者造成干擾。建議您先進行大量修正,縮小檢查範圍,或重新考慮檢查的實用性。
- 在樹狀結構中的所有檔案上執行 (除了 Git 忽略的檔案或
- 最後,請將檢查項目上傳至 Gerrit,執行提交前作業,並檢查失敗項目,目標是 0 個失敗項目。(預先提交的行為與執行
fx host-tool shac check --all
相同)
如果檢查項目為選擇加入 (不會在提交前執行),或是有不明確的選擇退出機制,建議您記錄檢查項目。所有文件都應新增至 //docs/development/source_code/presubmit_checks.md