iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
佛心分享-SideProject30

打造 APR Engineer 的生產力平台,從 Flow Tracer 到 SignOff DashBoard 的落地實戰系列 第 7

【Day 07 】 Flow Generator 架構設計:如何定義一個標準化的流程?

  • 分享至 

  • xImage
  •  

前言

從這篇開始,會真正打開 WinFlow 2.0 的 GUI。本系列不打算把每個函式都講一遍,目標比較務實:讓你能依照自己公司的口令,把 Flow 組出來。架構細節、開發筆記會另外放在 Repo 的 doc/,需要時再翻。

Repo:WinFlow 2.0

若 Linux 環境沒有 Tkinter,先補裝(常見於 WSL):

sudo apt update
sudo apt install python3-tk

LSF 也請先確認能動(見 Day 3)。完成後在 repo 根目錄執行:

python winflow_gui.py

先會用:從 example 範本長出一條 APR + QC

視窗開起來後,先切到 Generator
https://ithelp.ithome.com.tw/upload/images/20260811/20127932YBvTdVGECY.png

Template 清單會列出 blank,以及所有成功註冊進系統的 Flow。內建至少會看到 example。我們先選 example,再按 Load。

若按的是 Open,則是去開一份已經符合格式的 flow.json,不是載範本。

https://ithelp.ithome.com.tw/upload/images/20260811/20127932mwBF5phCi5.png

若按照前面幾天的設定,Queue 名稱多半是 normal。不確定就再跑一次 bqueues。下面的 Reference 今天先略過,直接按 Load Template

https://ithelp.ithome.com.tw/upload/images/20260811/20127932axwpQMWIoM.png

一條完整的 APR + QC Flow 會出現在畫布上。這就是昨天那份 JSON 的視覺版:flow 往右走,QC 往旁邊長。

接下來做一件範本裡沒有提供的事:新增一個 QC Summary。它接在 Route 後面,而且必須等所有 QC 都完成才能跑

Add Job,會看到目前已註冊的 Job 節點。把必要欄位填好,按住 Left Ctrl 把所有要當 Parent 的節點選起來,最後按 Save
https://ithelp.ithome.com.tw/upload/images/20260811/201279326bysKgESgM.png

GUI 做的事情,其實就是幫你寫昨天那些 parents / children。你選的是節點,系統寫的是 stage/task/job key。

Generator 在程式裡長什麼樣子

會用之後,來看它為什麼能「加一個資料夾就多一種範本」。目錄大致如下

winflow/generator/
├── __init__.py
├── __main__.py
├── cli.py                   # 命令列入口與流水線編排
├── core/
│   ├── builder.py           # FlowBuilder 抽象類
│   ├── registry.py          # @register / get_builder / list_flows
│   ├── context.py           # BuildContext
│   ├── models.py            # Flow/Stage/Task/Job + make_* 工廠
│   └── io.py                # 寫出 flow.json
├── parsers/                 # 讀設定檔,後面帶入 builder 會用到
│   ├── setting_sh.py        # 解析 csh set 語句
│   └── block_stream.py      # 解析 block 列表(example 未強制使用)
├── flows/
│   ├── __init__.py
│   └── example/             # **我們現在畫面上看到的**
│       ├── __init__.py
│       └── builder.py       # ExampleFlowBuilder
├── editor/                  # 畫布用的文件/依賴/佈局(不是 Tk 本體)
│   ├── document.py          # FlowDocument、模板載入、匯出排序
│   ├── deps.py              # link/unlink parents/children
│   ├── graph.py             # 畫布 JobGraph / layout
│   └── nodes.py             # node/*.json 模板庫讀寫
└── node/                    # Add Job 預置節點,JSON 放這裡
    ├── blank_job.json
    ├── FLOOR_PLAN.json
    ├── Q_FLOOR_PLAN.json
    └── ...

這裡有兩條完全不同的「註冊」:

你想加什麼 放哪裡 GUI 哪裡出現
一種完整 Flow 範本(一載入就是一張圖) flows/<name>/ + @register Template 下拉選單
一顆可重用的 Job 積木 generator/node/*.json Add Job 清單

不要把兩件事混在同一個資料夾。範本是「一整場蘿蔔蹲的預設口令」;積木是「臨時加進來的一個人」。

註冊新的 Template:先做一個叫 ithome 的空殼

假設我們要新增一種 Flow,名稱叫 ithome

├── flows/
│   ├── __init__.py          # 加上 from winflow.generator.flows import ithome
│   ├── example/
│   │   ├── __init__.py
│   │   └── builder.py
│   └── ithome/              # 新建這個資料夾
│       ├── __init__.py      # 把 ithomeFlowBuilder 匯出
│       └── builder.py

關鍵有三步,少一步 GUI 就看不見:

  1. builder.py 裡用 @register("ithome") 掛上 FlowBuilder 子類
  2. ithome/__init__.py 把 class 匯出
  3. flows/__init__.py import 這個 package,讓 decorator 在程式啟動時真的跑到

Template 下拉選單不是寫死 blank / example。它會去問 list_flows():誰成功註冊,誰就出現。所以 ithome 一掛上,重開 GUI 就會在清單裡。

先寫一個空的 builder.py

from __future__ import annotations
from dataclasses import dataclass
from typing import List

from winflow.config import get_config, get_section
from winflow.generator.core.builder import FlowBuilder
from winflow.generator.core.context import BuildContext
from winflow.generator.core.models import Flow, make_flow
from winflow.generator.core.registry import register

@dataclass(frozen=True)
class IthomeFlowConfig:
    """Defaults for the ithome flow; override via config.json without models.py."""

    flow_name: str = "ithome"

@register("ithome")
class ithomeFlowBuilder(FlowBuilder):
    """Build the bundled demo place-and-route style example flow."""

    @classmethod
    def validate_context(cls, context: BuildContext) -> List[str]:
        return []

    @classmethod
    def build(cls, context: BuildContext) -> Flow:
        cfg = get_section("ithome", IthomeFlowConfig())
        gen_cfg = get_config().generator
        stages = []
        return make_flow(cfg.flow_name, stages, poll_interval=gen_cfg.poll_interval)

get_section("ithome", IthomeFlowConfig()) 值得多看一眼:預設值活在這個 flow 自己的 dataclass 裡,不必去改中央的 models.pyconfig.json 若有 "ithome" 區塊會覆寫;沒有就用這裡的預設。新 flow 不該每次都去碰核心設定檔。

重開 GUI 後,Template 清單會出現 ithome。現在它還是空殼,畫布上沒有任何 Stage。接下來我們把"人"放進場。

把 ithome 長成一張有分叉的圖

預期結果:

IT_2 ______________
                   |
                   v
IT_1  —> IT_3  —> IT4  -> IT_5
           |                ^
           |________________|

口令翻譯成依賴:

  • IT_1IT_2 都是 Root,可以一起跑
  • IT_3IT_1
  • IT4IT_3IT_2
  • IT_5IT_3IT4(因此也間接等齊 IT_2

先補常用函式與預設設定:

from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional
from winflow.config import get_config, get_section
from winflow.generator.core.builder import FlowBuilder
from winflow.generator.core.context import BuildContext
from winflow.generator.core.models import Flow, Job, Stage, make_flow, make_job
from winflow.generator.core.registry import register

JOB_NAMES = ("IT_1", "IT_2", "IT_3", "IT4", "IT_5")

@dataclass(frozen=True)
class IthomeFlowConfig:
    """Defaults for the ithome flow; override via config.json without models.py."""
    flow_name: str = "ithome"
    script_dir: str = "ithome_flow"
    out_dir: str = "ithome_flow/out"
    command_template: str = "./{script_dir}/{job_name}.csh"
    output_template: str = "{out_dir}/{job_name}.done"
    default_queue: str = "normal"
    default_cpu: str = "1"
    
# 幫忙打包一些設定值用的 
def _cfg() -> IthomeFlowConfig:
    return get_section("ithome", IthomeFlowConfig())

def job_output_path(job_name: str) -> str:
    cfg = _cfg()
    return cfg.output_template.format(job_name=job_name, out_dir=cfg.out_dir)

def job_command(job_name: str) -> str:
    cfg = _cfg()
    return cfg.command_template.format(job_name=job_name, script_dir=cfg.script_dir)

# 這邊是因為設計架構上我讓每個 job 都有額外的 stage 跟 Task 的屬性
# 主要是希望建 flow 的人可以把階層區分好,也許有利以後的操作,但實際 Flow 在跑只會依賴 parent 跟 child 的關係
# 我們這邊就先偷懶讓 stage 跟 Task 都跟 job 同名
def _wrap_jobs_as_stages(jobs: List[Job]) -> List[Stage]:
    """Flow schema requires stage/task nesting; derive both from each job name."""
    stages: List[Stage] = []
    for job in jobs:
        name = job["name"]
        stages.append({"name": name, "tasks": [{"name": name, "jobs": [job]}]})
    return stages

重點在 build_ithome_stages。注意我們沒有手寫 parents / children,只宣告每個 job 的 input / output 檔。WinFlow 會在欄位還缺的時候,用檔案對應把依賴給補出來:

def build_ithome_stages(
    queue: Optional[str] = None,
    cpu: Optional[str] = None,
    machine: str = "",
) -> List[Stage]:
    cfg = _cfg()
    queue = queue if queue is not None else cfg.default_queue
    cpu = cpu if cpu is not None else cfg.default_cpu
    out = {name: job_output_path(name) for name in JOB_NAMES}
    # WinFlow2.0 會透過 in/output , 自動去生成 parent 跟 child 的關係
    # 使用者只需要哪些 job 結束後換誰跑就好了
    jobs = [
        make_job("IT_1", job_command("IT_1"), [], [out["IT_1"]], queue, cpu, machine),
        make_job("IT_2", job_command("IT_2"), [], [out["IT_2"]], queue, cpu, machine),
        make_job("IT_3", job_command("IT_3"), [out["IT_1"]], [out["IT_3"]], queue, cpu, machine),
        make_job("IT4", job_command("IT4"), [out["IT_3"], out["IT_2"]], [out["IT4"]], queue, cpu, machine),
        make_job("IT_5",job_command("IT_5"),[out["IT_3"], out["IT4"]],[out["IT_5"]],queue,cpu, machine,),
    ]
    return _wrap_jobs_as_stages(jobs)

讀這段時可以對著圖核對:

  • IT_1IT_2 的 inputs 是空的 → 兩個 Root
  • IT_3IT_1.done
  • IT4 同時吃 IT_3.doneIT_2.done → 匯流
  • IT_5IT_3.doneIT4.done

使用者在 builder 裡只要想「誰做完輪到誰」;檔案路徑就是那句口令的實體化。這樣就完成一種可載入的範本了。

添加新 Node:從匯出的 JSON 拆一顆積木出來

積木比範本更單純。準備好「單一 Job」的 JSON,放到 generator/node/ 即可。

我們直接用剛剛的 ithome。先按 Export flow.json

https://ithelp.ithome.com.tw/upload/images/20260811/20127932lPvDWGBpME.png

打開檔案,把其中一個 Job 區塊複製出來,並拿掉 parentschildren。積木不該帶上一次 DAG 的記憶,否則 Add Job 時會把舊邊一起貼進來。

IT_1 為例:把 blank_job.json 複製成 IT_1.json,改 flow_name、stage / task / job 名稱,再貼上這份設定:

{
  "flow_name": "ithome",
  "poll_interval": 20,
  "stages": [
    {
      "name": "IT_1",
      "tasks": [
        {
          "name": "IT_1",
          "jobs": [
            {
              "name": "IT_1",
              "command": "./ithome_flow/IT_1.csh",
              "queue": "normal",
              "cpu": 1,
              "inputs": [],
              "outputs": [
                "ithome_flow/out/IT_1.done"
              ]
            }
          ]
        }
      ]
    }
  ]
}

重開 GUI,再按一次 Add Job,清單裡就會出現這顆節點。

https://ithelp.ithome.com.tw/upload/images/20260811/20127932YskF5rSuM4.png

/images/emoticon/emoticon08.gif


小結

  • 先會用:Load example,再 Accel 加一顆 QC Summary
  • @register + import,Template 清單會自己出現新 flow
  • 預設設定可以活在 flow 自己的 dataclass,不必改中央 models.py
  • builder 用 input / output 描述「誰做完輪到誰」,缺依賴時系統會自動補 parents / children
  • stage / task 是分層標籤;開跑只認依賴
  • Add Job 的積木是去了依賴關係的迷你 JSON,放進 generator/node/

上一篇
【Day 06 】設計專屬你的 Flow:畢竟每家 IC 設計公司的環境都不一樣
下一篇
【Day 08 】 Flow Generator 靈魂擴充:透過範本與設定檔實現自動化生成
系列文
打造 APR Engineer 的生產力平台,從 Flow Tracer 到 SignOff DashBoard 的落地實戰14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言