這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go語言程式組織和構建的基本單元是Package,但Go語言官方卻沒有提供一款“像樣的”Package Management Tool(包管理工具)。隨著Go語言在全球範圍內應用的愈加廣泛,缺少官方包管理工具這一問題變得日益突出。
2016年GopherCon大會後,在Go官方的組織下,一個旨在改善Go包管理的commitee成立了,共同應對Go在package management上遇到的各種問題。經過各種腦洞和討論後,該commitee在若干月後發布了“Package Management Proposal”,並啟動了最有可能被接納為官方包管理工具的項目dep的設計和開發。2017年年初,dep項目正式對外開放。截至目前,dep發布了v0.1.0版本,並處於alpha測試階段。
可以說,dep的進展還是蠻快的。按照dep官方說法,dep目前的manifest和lock檔案格式已經stable,並保證向後相容。同時,dep實現了“自舉”,即dep使用自己作為自己的包管理工具。由於dep的“特殊身份”,雖然dep離成熟尚遠,但dep的進展也吸引了諸多gopher的目光,很多組織已經開始將package management tool遷移為dep,為dep進行早期測試。
這裡,我也打算“嘗嘗鮮”,在本篇文章中和大家一起窺探和試用一下dep。
一、Go包管理的演化曆史
1、go get
在管窺dep之前,我們先來簡單看看Go語言套件管理的演化曆史。首當其衝的就是go get。
Go語言新手在初次接觸Go語言時會感覺到Go語言的package擷取真的是很方便:只需一行go get xxx,github.com上的大量go package就可以隨你取用。 但隨著對Go語言使用的深入,人們會發現go get給我們帶來方便的同時,也帶來了不少的麻煩。go get本質上是git、hg等這些vcs工具的進階wrapper。對於使用git的go package來說,go get的實質就是將package git clone到本地的特定目錄下($GOPATH/src),同時go get可以自動解析包的依賴,並自動下載相關依賴包。
go get機制的設計很大程度上源於Google公司內部的單一root的代碼倉庫的開發模式,並且似乎google內部各個project/repository的master分支上的代碼都是被認為stable的,因此go get僅僅支援擷取master branch上的latest代碼,沒有指定version、branch或revision的能力。而在Google公司以外的世界裡,這樣的做法會給gopher帶來不便:依賴的第三方包總是在變。一旦第三方包提交了無法正常build或介面不相容的代碼,依賴方立即就會受到影響。
而gopher們又恰恰希望自己項目所依賴的第三方包能受到自己的控制,而不是隨意變化。這樣,godep、gb、glide等一批第三方包管理工具出現了。
以應用最為廣泛的godep為例。為了能讓第三方依賴包“穩定下來”,實現項目的reproduceble build,godep將項目當前依賴包的版本資訊記錄在Godeps/Godeps.json中,並將依賴包的相關版本存放在Godeps/_workspace中。在編譯時間(godep go build)godep通過臨時修改GOPATH環境變數的方法讓go編譯器使用緩衝在Godeps/_workspace下的項目依賴的特定版本的第三方包,這樣保證了項目不再受制於依賴的第三方包的master branch上的latest代碼的變動了。
不過,godep的“版本管理”本質上是通過緩衝第三方庫的某個revision的快照實現的,這種方式依然讓人感覺難於管理。同時,通過對GOPATH的“偷梁換柱”的方式實現使用Godeps/_workspace中的第三方庫的快照進行編譯也無法相容Go原生編譯器,必須使用godep go xxx來進行。
為此,Go進一步引入vendor機制來減少gopher在包管理問題上的心智負擔。
2、vendor機制
Go team也一直在關注Go語言套件依賴的問題,尤其是在Go 1.5實現自舉的情況下,官方同樣在1.5版本中推出了vendor機制。vendor機制是Russ Cox在Go 1.5發布前期以一個experiment feature身份緊急加入到go中的(go 1.6脫離experiment身份)。vendor標準化了項目依賴的第三方庫的存放位置(不再需要Godeps/_workspace了),同時也無需對GOPATH環境變數進行“偷梁換柱”了,go compiler原生優先感知和使用vendor下緩衝的第三方包。
不過即便有了vendor的支援,vendor內第三方依賴包的代碼的管理依舊是不規範的,要麼是手動的,要麼是藉助godep這樣的第三方包管理工具。目前自舉後的Go代碼本身也引入了vendor,不過go項目自身對vendor中代碼的管理方式也是手動更新,Go自身並未使用任何第三方的包管理工具。
題外話:作為一門語言的標準庫,應該是使用這門語言的開發人員所使用的所有lib依賴的根依賴。但在go中,go標準庫居然還要依賴golang.org/x/目錄下的包,既然能被std lib依賴,那麼說明其已經成熟,那為何不把x內的stable的庫挪到std lib中呢?這點著實讓人有些不解。
~/.bin/go18/src/vendor/golang_org/x]$lscrypto/ net/ text/
從Go官方角度出發,官方go包依賴的解決方案的下一步就應該是解決對vendor下的第三方包如何進行管理的問題:依賴包的分析、記錄和擷取等,進而實現項目的reproducible build。dep就是用來做這事兒的。
二、dep簡介
go package management commitee的牽頭人物是微服務架構go-kit作者Peter Bourgon,但當前主導dep開發的是sam boyer,sam也是dep底層包依賴分析引擎-gps的作者。
和其他一些第三方Go包管理工具有所不同,dep在進行active dev前是經過commitee深思熟慮的,包括:features、user story等都在事前做了初步設計。如果你拜讀這些文檔,你可能會覺得解決包依賴問題,還是蠻複雜的。不過,對於這些工具的使用者來說,我們面對的是一些十分簡化的互動介面。
1、安裝dep
dep是標準的go cli程式,執行一條命令即完成安裝:
# go get -u github.com/golang/dep/cmd/dep# dep helpdep is a tool for managing dependencies for Go projectsUsage: dep <command>Commands: init Initialize a new project with manifest and lock files status Report the status of the project's dependencies ensure Ensure a dependency is safely vendored in the project prune Prune the vendor tree of unused packagesExamples: dep init set up a new project dep ensure install the project's dependencies dep ensure -update update the locked versions of all dependencies dep ensure github.com/pkg/errors add a dependency to the projectUse "dep help [command]" for more information about a command.
在我的測試環境中,go的版本為1.8;dep的版本為commit d31c621c3381b9bebc7c10b1ac7849a96c21f2c3。
注意:由於dep還在active dev過程中且處於alpha測試階段,因此本文中執行的dep命令、命令列為以及輸出結果在後續dep版本中很可能會有變動,甚至是很大變動。
2、dep一般工作流程
安裝好dep後,我們就來看看使用dep的一般工作流程。我們首先準備一個demo程式:
//depdemo/main.gopackage mainimport ( "net/http" "go.uber.org/zap" "github.com/beego/mux")func main() { logger, _ := zap.NewProduction() defer logger.Sync() sugar := logger.Sugar() mx := mux.New() mx.Handler("GET", "/", http.FileServer(http.Dir("."))) sugar.Fatal(http.ListenAndServe("127.0.0.1:8080", mx))}
a) dep init
如果一個項目要使用dep進行包管理,那麼首先需要在這個項目的根下執行dep init。在這裡,我們對depdemo進行dep改造。
在depdemo目錄下,執行dep init:
# dep init -vSearching GOPATH for projects... Using master as constraint for direct dep github.com/beego/mux Locking in master (626af65) for direct dep github.com/beego/muxFollowing dependencies were not found in GOPATH. Dep will use the most recent versions of these projects. go.uber.org/zapRoot project is "github.com/bigwhite/experiments/depdemo" 1 transitively valid internal packages 2 external packages imported from 2 projects(0) ✓ select (root)(1) ? attempt github.com/beego/mux with 1 pkgs; at least 1 versions to try(1) try github.com/beego/mux@master(1) ✓ select github.com/beego/mux@master w/1 pkgs(2) ? attempt go.uber.org/zap with 1 pkgs; 12 versions to try(2) try go.uber.org/zap@v1.4.0(2) ✓ select go.uber.org/zap@v1.4.0 w/7 pkgs(3) ? attempt go.uber.org/atomic with 1 pkgs; 6 versions to try(3) try go.uber.org/atomic@v1.2.0(3) ✓ select go.uber.org/atomic@v1.2.0 w/1 pkgs ✓ found solution with 9 packages from 3 projectsSolver wall times by segment: b-source-exists: 1.090607387s b-deduce-proj-root: 288.126482ms b-list-pkgs: 131.059753ms b-gmal: 114.716587ms select-atom: 337.787µs satisfy: 298.743µs select-root: 292.889µs new-atom: 257.256µs b-list-versions: 42.408µs other: 22.307µs TOTAL: 1.625761599s
當前階段,dep init命令的執行效率的確不高,因此需要你耐心的等待一會兒。如果你的project依賴的外部包很多,那麼等待的時間可能會很長。並且由於dep會下載依賴包,對於國內的朋友來說,一旦下載qiang外的包,那麼dep可能會“阻塞”在那裡!
dep init大致會做這麼幾件事:
- 利用gps分析當前程式碼封裝中的包依賴關係;
- 將分析出的項目包的直接依賴(即main.go顯式import的第三方包,direct dependency)約束(constraint)寫入項目根目錄下的Gopkg.toml檔案中;
- 將項目依賴的所有第三方包(包括直接依賴和傳遞依賴transitive dependency)在滿足Gopkg.toml中約束範圍內的最新version/branch/revision資訊寫入Gopkg.lock檔案中;
- 建立root vendor目錄,並且以Gopkg.lock為輸入,將其中的包(精確checkout 到revision)下載到項目root vendor下面。
執行完dep init後,dep會在目前的目錄下產生若干檔案:
├── Gopkg.lock├── Gopkg.toml├── main.go└── vendor/
我們逐一來看一下:
Gopkg.toml:
[[constraint]] branch = "master" name = "github.com/beego/mux"[[constraint]] name = "go.uber.org/zap" version = "1.4.0"
Gopkg.toml記錄了depdemo/main.go的兩個direct dependency:mux和zap。通過gps的分析(可以參見上面init執行時輸出的詳細分析過程日誌),dep確定的依賴版本約束為:mux的master分支、zap的1.4.0 version。
產生的Gopkg.lock中則記錄了depdemo/main.go在上述約束下的所有依賴的可用的最新版本:
Gopkg.lock:[[projects]] branch = "master" name = "github.com/beego/mux" packages = ["."] revision = "626af652714cc0092f492644e298e5f3ac7db31a"[[projects]] name = "go.uber.org/atomic" packages = ["."] revision = "4e336646b2ef9fc6e47be8e21594178f98e5ebcf" version = "v1.2.0"[[projects]] name = "go.uber.org/zap" packages = [".","buffer","internal/bufferpool","internal/color","internal/exit","internal/multierror","zapcore"] revision = "fab453050a7a08c35f31fc5fff6f2dbd962285ab" version = "v1.4.0"[solve-meta] analyzer-name = "dep" analyzer-version = 1 inputs-digest = "77d32776fdc88e1025460023bef70534c5457bdc89b817c9bab2b2cf7cccb22f" solver-name = "gps-cdcl" solver-version = 1
vendor目錄下,則是lock檔案中各個依賴包的本地clone:
# tree -L 2 vendorvendor├── github.com│ └── beego└── go.uber.org ├── atomic └── zap
至此,dep init完畢,相關依賴包也已經被vendor,你可以使用go build/install進行程式構建了。
b)、提交Gopkg.toml和Gopkg.lock
如果你對dep自動分析出來的各種約束和依賴的版本沒有異議,那麼這裡就可以將Gopkg.toml和Gopkg.lock作為項目源碼的一部分提交到程式碼程式庫中了。這樣其他人在下載了你的代碼後,可以通過dep直接下載lock檔案中的第三方包版本,並存在vendor裡。這樣就使得無論在何處,項目構建的依賴庫理論上都是一致的,實現reproduceable build。
是否需要提交vendor下的依賴包代碼到代碼倉庫?這取決於你。提交vendor的好處是即便沒有dep,也可以實現真正的reproduceable build。但vendor的提交會讓你的程式碼程式庫變得異常龐大,且更新vendor時,大量的diff會影響到你對代碼的review。下面的內容我們以不提交vendor為前提。
c)、dep ensure
現在我們的depdemo已經加入了Gopkg.toml和Gopkg.lock。這時,如果你將depdemo clone到你的本地,你還無法進行reproduceable build,因為這時vendor還不存在。這時我們需要執行下面命令來根據Gopkg.toml和Gopkg.lock中的資料構建vendor目錄和同步裡面的包:
# dep ensure# ls -FGopkg.lock Gopkg.toml main.go vendor/
ensure成功後,你就可以進行reproduceable build了。
我們可以通過dep status查看當前的依賴情況(包括direct and transitive dependency):
# dep statusPROJECT CONSTRAINT VERSION REVISION LATEST PKGS USEDgithub.com/beego/mux branch master branch master 626af65 626af65 1go.uber.org/atomic * v1.2.0 4e33664 4e33664 1go.uber.org/zap ^1.4.0 v1.4.0 fab4530 fab4530 7
d) 指定約束
dep init產生的Gopkg.toml中的約束是否是我們預期的呢?這個還真不一定。比如:我們將對zap的約束手工改為1.3.0:
//Gopkg.toml... ...[[constraint]] name = "go.uber.org/zap" version = "<=1.3.0"
執行dep ensure後,查看status:
# dep statusPROJECT CONSTRAINT VERSION REVISION LATEST PKGS USEDgithub.com/beego/mux branch master branch master 626af65 626af65 1go.uber.org/atomic * v1.2.0 4e33664 4e33664 1go.uber.org/zap <=1.3.0 v1.4.0 fab4530 fab4530 7
不過,此時Gopkg.lock中的zap version依舊是v1.4.0,並沒有修改。要想更新lock和vendor下的資料,我們需要給ensure加上一個-update參數:
# dep ensure -update# git diff Gopkg.lockdiff --git a/depdemo/Gopkg.lock b/depdemo/Gopkg.lockindex fce53dc..7fe3640 100644--- a/depdemo/Gopkg.lock+++ b/depdemo/Gopkg.lock@@ -16,12 +16,12 @@ [[projects]] name = "go.uber.org/zap" packages = [".","buffer","internal/bufferpool","internal/color","internal/exit","internal/multierror","zapcore"]- revision = "fab453050a7a08c35f31fc5fff6f2dbd962285ab"- version = "v1.4.0"+ revision = "6a4e056f2cc954cfec3581729e758909604b3f76"+ version = "v1.3.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1- inputs-digest = "77d32776fdc88e1025460023bef70534c5457bdc89b817c9bab2b2cf7cccb22f"+ inputs-digest = "b09c1497771f6fe7cdfcf61ab1a026ccc909f4801c08f2c25f186f93f14526b0" solver-name = "gps-cdcl" solver-version = 1
-update讓dep ensure嘗試去保證並同步Gopkg.lock和vendor目錄下的資料,將Gopkg.lock下的zap的version改為Gopkg.toml下約束的最大值,即v1.3.0,同時更新vendor下的zap代碼。
e) 指定依賴
我們也可以直接更新dependency,這將影響Gopkg.lock和vendor下的資料,但Gopkg.toml不會被修改:
# dep ensure 'go.uber.org/zap@<1.4.0'# git diffdiff --git a/depdemo/Gopkg.lock b/depdemo/Gopkg.lockindex fce53dc..3b17b9b 100644--- a/depdemo/Gopkg.lock+++ b/depdemo/Gopkg.lock@@ -16,12 +16,12 @@ [[projects]] name = "go.uber.org/zap" packages = [".","buffer","internal/bufferpool","internal/color","internal/exit","internal/multierror","zapcore"]- revision = "fab453050a7a08c35f31fc5fff6f2dbd962285ab"- version = "v1.4.0"+ revision = "6a4e056f2cc954cfec3581729e758909604b3f76"+ version = "v1.3.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1- inputs-digest = "77d32776fdc88e1025460023bef70534c5457bdc89b817c9bab2b2cf7cccb22f"+ inputs-digest = "3307cd7d5942d333c4263fddda66549ac802743402fe350c0403eb3657b33b0b" solver-name = "gps-cdcl" solver-version = 1
這種情況下會出現Gopkg.lock中的version不滿足Gopkg.toml中約束的情況。這裡也讓我比較困惑!
三、dep探索
上面的dep使用基本工作流程完全可以滿足日常包管理的需求了。但對於喜歡求甚解的我來說,必要要探索一下dep背後的行為和原理。
1、dep init的兩種不同結果
我們回到depdemo的初始狀態,即起點:尚未產生dep metadata file的時刻。我們在兩種情況下,分別執行dep init:
- $GOPATH/src下沒有go.uber.org/zap
# dep init -vSearching GOPATH for projects... Using master as constraint for direct dep github.com/beego/mux Locking in master (626af65) for direct dep github.com/beego/muxFollowing dependencies were not found in GOPATH. Dep will use the most recent versions of these projects. go.uber.org/zapRoot project is "github.com/bigwhite/experiments/depdemo" 1 transitively valid internal packages 2 external packages imported from 2 projects... ...# dep statusPROJECT CONSTRAINT VERSION REVISION LATEST PKGS USEDgithub.com/beego/mux branch master branch master 626af65 626af65 1go.uber.org/atomic * v1.2.0 4e33664 4e33664 1go.uber.org/zap ^1.4.0 v1.4.0 fab4530 fab4530 7
- $GOPATH/src下存在go.uber.org/zap
# dep init -vSearching GOPATH for projects... Using master as constraint for direct dep github.com/beego/mux Locking in master (626af65) for direct dep github.com/beego/mux Using master as constraint for direct dep go.uber.org/zap Locking in master (b33459c) for direct dep go.uber.org/zap Locking in master (908889c) for transitive dep go.uber.org/atomicRoot project is "github.com/bigwhite/experiments/depdemo" 1 transitively valid internal packages 2 external packages imported from 2 projects... ...# dep statusPROJECT CONSTRAINT VERSION REVISION LATEST PKGS USEDgithub.com/beego/mux branch master branch master 626af65 626af65 1go.uber.org/atomic * branch master 908889c 4e33664 1go.uber.org/zap branch master branch master b33459c b33459c 7
不知道大家發現兩種情況下產生的結果的異同與否。我們只看兩個dep status輸出中的zap一行:
go.uber.org/zap ^1.4.0 v1.4.0 fab4530 fab4530 7vs.go.uber.org/zap branch master branch master b33459c b33459c 7
dep自動分析後得到截然不同的兩個結果。
第一種情況,我們稱之為dep init的network mode,即dep發現本地GOPATH下面沒有zap,於是dep init通過network到upstream上尋找zap,並“Dep will use the most recent versions of these projects”,即v1.4.0版本。
第二種情況,我們稱之為dep init的GOPATH mode, 即dep發現本地GOPATH下面存在zap,於是dep init認定“Using master as constraint for direct dep go.uber.org/zap”,即master branch。
至於為何GOPATH mode下,dep init會選擇master,我個人猜測是因為dep覺得既然你本地有zap,那很大可能zap master的穩定性是被你所接受了的。在“dep: updated command spec”中,似乎dep init打算通過增加一個-gopath的flag來區分兩種工作模式,並將network mode作為預設工作mode。但目前我所使用的dep版本還沒有實現這個功能,其預設工作方式依舊是先GOPATH mode,如果沒有找到依賴包的存在,則針對該包實施network mode。
從這裡也可以看得出來,對於dep init 輸出的約束,你最好還是檢視一下,看是否能接受,否則就通過上面提到的“指定約束”來更正dep的輸出。
2、dep對項目的依賴包的cache
在進行上面的實驗中,我們發現:在本地GOPATH/src下面沒有zap的情況下,dep似乎是直接將zap get到本地vendor目錄的,而不是先get到GOPATH/src下,在copy到vendor中。事實是什麼樣的呢?dep的確沒有操作GOPATH/src目錄,因為那是共用的。dep在$GOPATH/pkg/dep/sources下留了一塊“自留地”,用於cache所有從network上下載的依賴包:
# ls -F $GOPATH/pkg/dep/sources/https---github.com-beego-mux/ https---github.com-uber--go-atomic/ https---github.com-uber--go-zap/# ls -aF /root/go/pkg/dep/sources/https---github.com-uber--go-zap./ buffer/ config_test.go field.go .gitignore http_handler.go LICENSE.txt options.go sugar.go writer.go../ CHANGELOG.md CONTRIBUTING.md field_test.go glide.lock http_handler_test.go logger_bench_test.go README.md sugar_test.go writer_test.goarray.go check_license.sh* doc.go flag.go glide.yaml internal/ logger.go .readme.tmpl time.go zapcore/array_test.go common_test.go encoder.go flag_test.go global.go level.go logger_test.go stacktrace.go time_test.go zapgrpc/benchmarks/ config.go encoder_test.go .git/ global_test.go level_test.go Makefile stacktrace_test.go .travis.yml zaptest/
dep對於依賴包的所以git請求均在這個緩衝目錄下進行。
3、 vendor flatten平坦化
go在1.5加入vendor機制時,是考慮到“鑽石形依賴”中存在同一個依賴包的不同版本的。我們來看看dep是否支援這一點。我們設計了一個實驗:
我們建立一個這樣的“鑽石形”實驗環境,foo依賴a、b兩個包,而a、b兩個包分別依賴f的不同版本(通過在a、b中的Gopkg.toml聲明這種約束,見圖中標註)。
下面是foo項目下面的main.go:
// foo/main.gopackage mainimport "bitbucket.org/bigwhite/b"import "bitbucket.org/bigwhite/a"func main() { a.CallA() b.CallB()}
未引入dep前,我們來運行一下該代碼:
$go run main.gocall A: master branch --> call F: call F: v1.1.0 --> call F endcall B: master branch --> call F: call F: v2.0.1 --> call F end
可以看到同樣是f包的輸出,由於a、b分別依賴f的不同版本,因此輸出不同。
我們對foo進行一個dep 分析,看看dep給了我們什麼結果:
$dep init -vSearching GOPATH for projects... Using master as constraint for direct dep bitbucket.org/bigwhite/a Locking in master (9122a5d) for direct dep bitbucket.org/bigwhite/a Using master as constraint for direct dep bitbucket.org/bigwhite/b Locking in master (2415845) for direct dep bitbucket.org/bigwhite/b Locking in master (971460c) for transitive dep bitbucket.org/bigwhite/fRoot project is "Foo" 1 transitively valid internal packages 2 external packages imported from 2 projects ... ...No versions of bitbucket.org/bigwhite/b met constraints: master: Could not introduce bitbucket.org/bigwhite/b@master, as it has a dependency on bitbucket.org/bigwhite/f with constraint ^2.0.0, which has no overlap with existing constraint ^1.1.0 from bitbucket.org/bigwhite/a@master v2.0.0: Could not introduce bitbucket.org/bigwhite/b@v2.0.0, as it is not allowed by constraint master from project Foo. v1.0.0: Could not introduce bitbucket.org/bigwhite/b@v1.0.0, as it is not allowed by constraint master from project Foo. master: Could not introduce bitbucket.org/bigwhite/b@master, as it has a dependency on bitbucket.org/bigwhite/f with constraint ^2.0.0, which has no overlap with existing constraint ^1.1.0 from bitbucket.org/bigwhite/a@master
dep init運行失敗。由於a依賴的f@^1.1.0和b依賴的f@^2.0.0兩個約束之間沒有交集,無法調和,dep無法solve這個依賴,於是init failed!
但失敗背後還有一層原因,那就是dep的設計要求flatten vendor,即使用dep的項目只能有一個root vendor,所以直接依賴或傳遞依賴的包中包含vendor的,vendor目錄也都會被strip掉。這樣一旦依賴包中存在帶有衝突的約束,那麼dep init必將失敗。
四、小結
dep一個重要feature就是支援semver 2.0規範,不過semver的規則好多,不是這裡能說清楚的,大家可以到semver官方站細讀規則,或者在npm semver calculator這個網站直觀感受semver規則帶來的變化。
dep實驗告一段落。從目前來看,dep已經進入可用階段,建議有條件的童鞋能積極的使用dep,並為dep進行前期測試,發現問題提issue,為dep的快速完善出出力。
depdemo的代碼在這裡;a, b,f包的代碼在這裡、這裡和這裡。
五、參考資料
- dep FAQ
- dep roadmap
- dep updated command spec
- dep features
- The Saga of Go Dependency Management by sam boyer
- gps for implementors
微博:@tonybai_cn
公眾號:iamtonybai
github.com: https://github.com/bigwhite
2017, bigwhite. 著作權.
Related posts:
- 理解Go 1.5 vendor
- Go 1.6中值得關注的幾個變化
- godep支援Go 1.5 vendor
- CVS Primer
- Advanced CVS