Bu belgede, kod tabanı ve Bazel'ın nasıl yapılandırıldığı açıklanmaktadır. Bu program son kullanıcılar için değil, Bazel'a katkıda bulunmak isteyen kişiler için tasarlanmıştır.
Giriş
Bazel'in kod tabanı büyüktür (~350KLOC üretim kodu ve yaklaşık 260 KLOC test kodu) ve çevrenin tamamına kimse aşina değildir: Herkes kendi vadisini çok iyi bilmektedir, ama her yönde tepeliklerin aşıldığını çok az kişi bilebilir.
Yolculuğun ortasında olan kullanıcıların, kaybolan basit bir yolda kendilerini ormanın içinde bulmamaları için, bu belgede çalışmaya başlamayı kolaylaştırmak amacıyla kod tabanıyla ilgili bir genel bakış sağlamaya çalışılır.
Bazel'ın kaynak kodunun herkese açık sürümü, GitHub'da github.com/bazelbuild/bazel adresinde bulunur. Bu bilgi "bilgi kaynağı" değildir; Google dışında yararlı olmayan ek işlevler içeren Google dahili bir kaynak ağacından türetilmiştir. Uzun vadeli hedefimiz, GitHub'ı bilgi kaynağı hâline getirmek.
Katkılar, normal GitHub pull isteği mekanizması üzerinden kabul edilir, bir Google çalışanı tarafından manuel olarak dahili kaynak ağacına içe aktarılır, ardından tekrar GitHub'a aktarılır.
İstemci/sunucu mimarisi
Bazel'in büyük kısmı, derlemeler arasında RAM'de kalan bir sunucu işleminde yer alır. Bu, Bazel'in derlemeler arasındaki durumu korumasına olanak tanır.
Bu nedenle Bazel komut satırında başlatma ve komut olmak üzere iki tür seçenek bulunur. Aşağıdaki gibi bir komut satırında:
bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar
Bazı seçenekler (--host_jvm_args=
), çalıştırılacak komutun adından önce, bazıları (-c opt
) sonrasındadır. İlk tür, "başlangıç seçeneği" olarak adlandırılır ve sunucu işlemini bir bütün olarak etkiler. İkinci tür ise "komut seçeneği" yalnızca tek bir komutu etkiler.
Her sunucu örneğinin tek bir ilişkili kaynak ağacı ("çalışma alanı") vardır ve her çalışma alanı genellikle tek bir etkin sunucu örneği içerir. Bu, özel bir çıktı tabanı belirtilerek aşılabilir (daha fazla bilgi için "Dizin düzeni" bölümüne bakın).
Bazel, aynı zamanda geçerli bir .zip dosyası olan tek bir ELF yürütülebilir dosyası olarak dağıtılır.
bazel
yazdığınızda, C++'da uygulanan yukarıdaki ELF yürütülebilir dosyası ("istemci") kontrolü alır. Aşağıdaki adımları uygulayarak uygun bir sunucu işlemi oluşturur:
- Dosyanın daha önce çıkarılıp çıkarılmadığını kontrol eder. İlişkilendirilmediğinde bu mümkün değildir. Sunucunun uygulanması buradan gelir.
- Çalışan bir etkin sunucu örneği olup olmadığını kontrol eder: Örnek çalışır, doğru başlangıç seçeneklerine sahiptir ve doğru çalışma alanı dizinini kullanır.
$OUTPUT_BASE/server
dizinine bakarak çalışan sunucuyu bulur. Bu dizinde, sunucunun dinlediği bağlantı noktasıyla birlikte bir kilit dosyasının bulunduğu bir kilit dosyası bulunur. - Gerekirse eski sunucu işlemini sonlandırır
- Gerekirse yeni bir sunucu işlemi başlatır
Uygun bir sunucu işlemi hazır olduktan sonra, çalıştırılması gereken komuta bir gRPC arayüzü üzerinden iletilir. Daha sonra, Bazel'in çıkışı tekrar terminale iletilir. Aynı anda yalnızca bir komut çalışabilir. Bu uygulama, parçaları C++ ve Java'da bulunan parçaların yer aldığı ayrıntılı bir kilitleme mekanizması kullanılarak uygulanır. bazel version
komutunu başka bir komutla paralel olarak çalıştıramamak biraz utanç verici olduğundan birden fazla komutu paralel olarak çalıştırmak için bazı altyapı vardır. Ana engelleyici, BlazeModule
'lerin yaşam döngüsü ve
BlazeRuntime
içindeki bir durumdur.
Bir komutun sonunda, Bazel sunucusu istemcinin döndürmesi gereken çıkış kodunu iletir. İlginç bir kırışıklık ise bazel run
'nin uygulanmasıdır: Bu komutun işi Bazel'ın yeni oluşturduğu bir şeyi çalıştırmaktır ancak bir terminali olmadığından sunucu işleminden bunu yapamaz. Bunun yerine, istemciye hangi ikili programı ve hangi bağımsız değişkenleri ujexec()
seçmesi gerektiğini söyler.
Kullanıcı Ctrl-C tuşlarına bastığında, istemci bunu gRPC bağlantısında bir İptal çağrısına çevirir ve daha sonra komutu mümkün olan en kısa sürede sonlandırmaya çalışır. Üçüncü Ctrl-C'den sonra istemci, bunun yerine sunucuya bir SIGKILL gönderir.
İstemcinin kaynak kodu src/main/cpp
altında ve sunucuyla iletişim kurmak için kullanılan protokol src/main/protobuf/command_server.proto
içindedir .
Sunucunun ana giriş noktası BlazeRuntime.main()
şeklindedir ve istemciden gelen gRPC çağrıları GrpcServerImpl.run()
tarafından işlenir.
Dizin düzeni
Bazel, derleme sırasında biraz karmaşık bir dizin grubu oluşturur. Tam açıklamayı Çıkış dizini düzeninde bulabilirsiniz.
"Çalışma alanı", Bazel'in çalıştırıldığı kaynak ağacıdır. Genellikle kaynak kontrolünde kontrol ettiğiniz bir şeye karşılık gelir.
Bazel, tüm verilerini "çıkış kullanıcısı kökü" altına yerleştirir. Bu değer genellikle $HOME/.cache/bazel/_bazel_${USER}
değerine sahiptir ancak --output_user_root
başlangıç seçeneği kullanılarak geçersiz kılınabilir.
"Yükleme tabanı", Bazel'in çıkarıldığı yerdir. Bu işlem otomatik olarak yapılır ve her Bazel sürümü, yükleme tabanındaki sağlama toplamına göre bir alt dizin alır. Varsayılan olarak $OUTPUT_USER_ROOT/install
konumundadır ve --install_base
komut satırı seçeneği kullanılarak değiştirilebilir.
"Çıkış tabanı", belirli bir çalışma alanına ekli Bazel örneğinin yazdığı yerdir. Her çıkış tabanında aynı anda çalışan en fazla bir Bazel sunucu örneği bulunur. Normalde saat $OUTPUT_USER_ROOT/<checksum of the path
to the workspace>
. --output_base
başlatma seçeneği kullanılarak değiştirilebilir. Bu seçenek, diğer özelliklerin yanı sıra herhangi bir çalışma alanında aynı anda yalnızca bir Bazel örneğinin çalıştırılabileceği sınırlamayı aşmak için kullanışlıdır.
Çıkış dizini şunları içerir:
$OUTPUT_BASE/external
itibarıyla getirilen harici depolar.- Geçerli derlemenin tüm kaynak kodunun sembolik bağlantılarını içeren bir dizin olan exec kökü. Bulunduğu yer:
$OUTPUT_BASE/execroot
. Derleme sırasında, çalışma dizini$EXECROOT/<name of main repository>
olur. Uyumsuz bir değişiklik olduğu için uzun vadeli bir plan olsa da bunu$EXECROOT
olarak değiştirmeyi planlıyoruz. - Derleme sırasında oluşturulan dosyalar.
Komut çalıştırma işlemi
Bazel sunucusu kontrolü ele alıp yürütmesi gereken komut hakkında bilgi aldıktan sonra, sırasıyla aşağıdaki olaylar gerçekleşir:
BlazeCommandDispatcher
yeni istek hakkında bilgilendirildi. Komutun çalışması için bir çalışma alanına ihtiyaç duyup duymadığına (sürüm veya yardım gibi kaynak koduyla ilgisi olmayan komutlar dışında neredeyse her komut) ve başka bir komutun çalışıp çalışmadığına karar verir.Doğru komut bulundu. Her komut,
BlazeCommand
arayüzünü uygulamalı ve@Command
ek açıklamasına sahip olmalıdır (bu biraz ters örüntülü bir örnektir, bir komutun ihtiyaç duyduğu tüm meta verilerBlazeCommand
yönteminde açıklanmış olsaydı iyi olurdu)Komut satırı seçenekleri ayrıştırılır. Her komutun farklı komut satırı seçenekleri vardır. Bunlar
@Command
ek açıklamasında açıklanmıştır.Bir etkinlik yolu oluşturulur. Etkinlik veri yolu, derleme sırasında gerçekleşen etkinliklerin akışıdır. Derlemenin nasıl gittiğini dünyaya anlatmak için, Derleme Etkinliği Protokolü gözetimi altında bunlardan bazıları Bazel dışına aktarılır.
Kontrol, komuta geçer. En ilginç komutlar, derleme, test, çalıştırma, kapsam ve benzeri işlemleri çalıştıran komutlardır: Bu işlev
BuildTool
tarafından uygulanır.Komut satırındaki hedef kalıpları grubu ayrıştırılır ve
//pkg:all
ile//pkg/...
gibi joker karakterler çözümlenir. Bu,AnalysisPhaseRunner.evaluateTargetPatterns()
üzerinde uygulandı ve Skyframe'deTargetPatternPhaseValue
olarak yeniden düzenlendi.Yükleme/analiz aşaması, eylem grafiğini (derleme için yürütülmesi gereken komutların yönlendirilmiş bir döngüsel grafiği) üretmek için çalıştırılır.
Yürütme aşaması başlar. Bu, istenen üst düzey hedefleri oluşturmak için gereken her işlemin çalıştırılması anlamına gelir.
Komut satırı seçenekleri
Bazel çağrısı için komut satırı seçenekleri, OptionsParsingResult
nesnesinde açıklanmıştır. Bu nesne de "seçenek sınıflarından" seçeneklerin değerlerine bir harita içerir. "Seçenek sınıfı", OptionsBase
'ın bir alt sınıfıdır ve birbiriyle alakalı komut satırı seçeneklerini gruplandırır. Örneğin:
- Bir programlama diliyle (
CppOptions
veyaJavaOptions
) ilgili seçenekler. Bunlar,FragmentOptions
alt sınıfı olmalıdır ve sonunda birBuildOptions
nesnesine sarmalanır. - Bazel'in işlemleri yürütme şekliyle ilgili seçenekler (
ExecutionOptions
)
Bu seçenekler analiz aşamasında ve (Java'da RuleContext.getFragment()
veya Starlark'ta ctx.fragments
aracılığıyla) tüketilmek üzere tasarlanmıştır.
Bunlardan bazıları (örneğin, C++ yapılıp yapılmayacağına tarama yapılıp yapılmayacağına bakılmaksızın) yürütme aşamasında okunur. Ancak BuildConfiguration
o sırada kullanılamadığı için her zaman açık bir tesisat kurulumu gerekir. Daha fazla bilgi için "Yapılandırmalar" bölümüne bakın.
UYARI: OptionsBase
örneklerinin sabit olduğunu varsaymayı ve bunları bu şekilde kullanmayı (SkyKeys
öğesinin bir parçası gibi) kullanmayı seviyoruz. Böyle bir durum söz konusu değildir. Bunların değiştirilmesi, Bazel'ı hata ayıklaması zor
şekillerde bozmanın gerçekten iyi bir yoludur. Maalesef bunları değiştirilemez hale getirmek büyük bir iştir.
(Yapım aşamasından hemen sonra FragmentOptions
üzerinde değişiklik yapmadan önce ve equals()
ya da hashCode()
çağrılmadan önce bir değişiklik yapmada herhangi bir sakınca yoktur.)
Bazel, seçenek sınıflarını aşağıdaki yöntemlerle öğrenir:
- Bazıları Bazel'e kabloyla bağlı (
CommonCommandOptions
) - Her Bazel komutundaki
@Command
ek açıklamasından ConfiguredRuleClassProvider
sürümünden (bunlar, ayrı programlama dilleriyle ilgili komut satırı seçenekleridir)- Starlark kuralları kendi seçeneklerini de tanımlayabilir (buraya göz atın)
Her seçenek (Starlark tarafından tanımlanan seçenekler hariç), @Option
ek açıklamasına sahip bir FragmentOptions
alt sınıfının üye değişkenidir. Bu alt sınıf, komut satırı seçeneğinin adını ve türünü bazı yardım metniyle birlikte belirtir.
Bir komut satırı seçeneği değerinin Java türü genellikle basit bir şeydir (dize, tam sayı, Boole, etiket vb.). Bununla birlikte, daha karmaşık türler seçeneklerini de destekliyoruz. Bu durumda, komut satırı dizesinden veri türüne dönüştürme işi com.google.devtools.common.options.Converter
uygulanır.
Bazel'in gördüğü şekliyle kaynak ağaç
Bazel, kaynak kodu okuyup yorumlayarak yazılım oluşturma işiyle uğraşıyor. Bazel'in üzerinde çalıştığı kaynak kodunun bütünlüğü "çalışma alanı" olarak adlandırılır ve depolar, paketler ve kurallar halinde yapılandırılır.
Depolar
"Depo", geliştiricinin üzerinde çalıştığı bir kaynak ağacıdır ve genellikle tek bir projeyi temsil eder. Bazel'in atası Blaze, derlemeyi çalıştırmak için kullanılan tüm kaynak kodunu içeren tek bir kaynak ağaç olan bir monorepo üzerinde çalıştırılıyordu. Buna karşın Bazel, kaynak kodu birden fazla depoya yayılan projeleri destekler. Bazel'in çağrıldığı depoya "ana depo", diğerlerine ise "harici depolar" adı verilir.
Depo, kök dizininde WORKSPACE
(veya WORKSPACE.bazel
) adlı bir dosyayla işaretlenir. Bu dosya, derlemenin tamamı için "genel" bilgileri (örneğin, kullanılabilir harici depo grubu) içerir. Normal bir Starlark dosyası gibi
çalışır, yani biri diğer Starlark dosyalarını load()
.
Bu genellikle açıkça referans verilen deponun ihtiyaç duyduğu depoları çekmek için kullanılır (buna "deps.bzl
kalıbı" denir)
Harici depoların kodları $OUTPUT_BASE/external
altında sembolik bir şekilde bağlanır veya indirilir.
Derlemeyi çalıştırırken kaynak ağacın tamamının bir araya getirilmesi gerekir. Bu işlem, SymlinkForest
tarafından yapılır. Bu işlem, ana depodaki her paketi $EXECROOT
ile, harici depoları da $EXECROOT/external
veya $EXECROOT/..
'a eşler. (İlk depoda external
adlı bir paketin ana depoda bulunması elbette imkansızdır. Bu nedenle buradan taşıma işlemini gerçekleştiriyoruz.)
Paketler
Her depo; paketler, ilgili dosyalar koleksiyonu ve bağımlılıkların spesifikasyonundan oluşur. Bunlar, BUILD
veya BUILD.bazel
adlı bir dosyayla belirtilir. İkisi de mevcutsa Bazel BUILD.bazel
dosyasını tercih eder. BUILD
dosyanın hâlâ kabul edilmesinin nedeni, Bazel'in üst öğesi olan Blaze'in bu dosya adını kullanmasıdır. Ancak bu yol segmenti, özellikle dosya adlarının büyük/küçük harfe duyarlı olmadığı Windows'da yaygın olarak kullanılan bir yol segmentidir.
Paketler birbirinden bağımsızdır: Bir paketin BUILD
dosyasında yapılan değişiklikler, diğer paketlerin değişmesine neden olamaz. Yinelenen glob'lar paket sınırlarında durduğu için BUILD
dosyasının eklenmesi veya kaldırılması diğer paketleri _değiştirir. Böylece BUILD
dosyasının varlığı yinelemeyi durdurur.
BUILD
dosyasının değerlendirilmesine "paket yükleme" adı verilir. PackageFactory
sınıfında uygulandı, Starlark çevirmenini çağırarak çalışır ve mevcut kural sınıfları kümesi hakkında bilgi sahibi olmayı gerektirir. Paket yüklemenin sonucu bir Package
nesnesidir. Çoğunlukla, bir dizeden (bir hedefin adı) hedefin kendisine uzanan bir haritadır.
Paket yükleme sırasındaki karmaşıklığın büyük bir kısmı "globbing" işlemidir: Bazel, her kaynak dosyanın açıkça listelenmesini gerektirmez ve bunun yerine glob'ları (glob(["**/*.java"])
gibi) çalıştırabilir. Kabuktan farklı olarak, alt dizinlere inen (alt paketlere değil) yinelemeli glob'ları destekler. Bu, dosya sistemine erişim gerektirir. Bu yavaş olabileceğinden, sistemin paralel ve mümkün olduğunca verimli bir şekilde çalışmasını sağlamak için her türlü hileyi uygularız.
Globbing aşağıdaki sınıflarda uygulanır:
LegacyGlobber
, hızlı ve keyifli bir SkyFrame'in farkında olmayan küresel küreSkyframeHybridGlobber
, Skyframe'i kullanan ve "Skyframe yeniden başlatılması"ndan kaçınmak için eski globber'a geri dönen bir sürüm (aşağıda açıklanmıştır)
Package
sınıfının kendisi, özel olarak WORKSPACE dosyasını ayrıştırmak için kullanılan ve gerçek paketler için bir anlam ifade etmeyen bazı üyeler içerir. Normal paketleri tanımlayan nesnelerin başka bir şeyi açıklayan alanlar içermemesi gerektiğinden bu bir tasarım hatasıdır. Bunlardan bazıları:
- Depo eşlemeleri
- Kayıtlı araç zincirleri
- Kayıtlı yürütme platformları
İdeal olarak, WORKSPACE dosyasını ayrıştırmak ile normal paketleri ayrıştırmak arasında daha fazla ayrım olması gerekir. Böylece Package
, her ikisinin de ihtiyaçlarını karşılamaya gerek kalmaz. Bu ikisi birbiriyle iç içe geçmiş olduğundan maalesef bunu yapmak
zordur.
Etiketler, Hedefler ve Kurallar
Paketler, aşağıdaki türlerde hedeflerden oluşur:
- Dosyalar: Derlemenin girdisi veya çıktısı olan öğeler. Bazel dilinde bunlara eser (başka bir yerde tartışılan) diyoruz. Derleme sırasında oluşturulan tüm dosyalar hedef değildir. Bazel çıktılarının ilişkili bir etiketinin olmaması yaygın bir durumdur.
- Kurallar: Bunlar, girişlerden çıkışları elde etmeye yönelik adımları açıklar. Genellikle bir programlama diliyle (ör.
cc_library
,java_library
veyapy_library
) ilişkilendirilir ancak dilden bağımsız bazı diller de vardır (genrule
veyafilegroup
gibi). - Paket grupları: Görünürlük bölümünde tartışılır.
Hedefin adına Etiket adı verilir. Etiketlerin söz dizimi @repo//pac/kage:name
şeklindedir. Burada repo
, Etiketin bulunduğu deponun adıdır. pac/kage
, BUILD
dosyasının bulunduğu dizindir ve name
, paketin dizinine göre dosyaların yoludur (etiket bir kaynak dosyaya işaret ediyorsa). Komut satırında bir hedefe atıfta bulunurken etiketin bazı bölümleri atlanabilir:
- Depo çıkarılırsa etiket, ana depoya alınır.
- Paket bölümü çıkarılırsa (
name
veya:name
gibi) etiket, geçerli çalışma dizininin paketine dahil edilir (üst düzey referanslar (..) içeren göreli yollara izin verilmez)
Bir kural türüne ("C++ kitaplığı" gibi) "kural sınıfı" adı verilir. Kural sınıfları Starlark'ta (rule()
işlevi) veya Java'da (diğer adıyla "yerel kurallar", RuleClass
türü) uygulanabilir. Uzun vadede, dile özgü her kural Starlark'ta uygulanacak, ancak bazı eski kural aileleri (Java veya C++ gibi) şimdilik hâlâ Java'dadır.
Starlark kural sınıflarının, BUILD
dosyalarının başında load()
ifadesi kullanılarak içe aktarılması gerekirken Java kural sınıfları ConfiguredRuleClassProvider
öğesine kaydedildiğinden Bazel tarafından "doğuştan" bilinmektedir.
Kural sınıfları aşağıdaki gibi bilgileri içerir:
- Özellikleri (ör.
srcs
,deps
): türleri, varsayılan değerleri, kısıtlamaları vb. - Varsa her bir özelliğe ekli yapılandırma geçişleri ve unsurlar
- Kuralın uygulanması
- "Genellikle" kuralının oluşturduğu geçişli bilgi sağlayıcılar
Terminoloji notu: Kod tabanında, genellikle bir kural sınıfı tarafından oluşturulan hedefi ifade etmek için "Kural" kullanılır. Ancak Starlark'ta ve kullanıcılara yönelik belgelerde, "Kural" yalnızca kural sınıfını belirtmek için kullanılmalıdır. Hedef yalnızca bir "hedeftir". Ayrıca RuleClass
adında "class" olmasına rağmen bir kural sınıfı ile bu türdeki hedefler arasında Java devralma ilişkisi yoktur.
Gökyüzü Çerçevesi
Bazel'in temelini oluşturan değerlendirme çerçevesi Skyframe'dir. Modeli, bir derleme sırasında oluşturulması gereken her şeyin, kenarları herhangi bir veri parçasından bağımlılıklarına, yani yapıyı oluşturmak için bilinmesi gereken diğer veri parçalarına işaret eden, yönlendirilmiş bir döngüsel grafik halinde düzenlenir.
Grafikteki düğümlere SkyValue
ve adları SkyKey
olarak adlandırılır. Her ikisi de tamamen sabittir; bunlardan yalnızca sabit nesnelere erişilebilmelidir. Bu değişken, hemen hemen her zaman geçerlidir ve geçerli değilse (örneğin, BuildConfigurationValue
ve SkyKey
grubunun üyesi olan BuildOptions
bağımsız seçenek sınıfları için) bunları değiştirmemeye veya yalnızca dışarıdan gözlemlenemeyecek şekilde değiştirmemeye büyük çaba harcarız.
Bu doğrultuda, Skyframe içinde hesaplanan her şeyin (yapılandırılmış hedefler gibi) sabit olması gerektiği sonucuna varır.
Skyframe grafiğini gözlemlemenin en uygun yolu bazel dump
--skyframe=deps
komutunu çalıştırmaktır. Bu işlem, grafiğin dökümünü her satıra bir SkyValue
olacak şekilde atar. Bunu çok küçük yapılarda yapmak
en iyisidir çünkü çok büyük olabiliyor.
Skyframe, com.google.devtools.build.skyframe
paketinde bulunur. Benzer ada sahip com.google.devtools.build.lib.skyframe
paketi, Skyframe'in üzerine Bazel uygulamasını içerir. Skyframe hakkında daha fazla bilgiye buradan ulaşabilirsiniz.
Skyframe, belirli bir SkyKey
öğesini SkyValue
olarak değerlendirmek için anahtar türüne karşılık gelen SkyFunction
yöntemini çağırır. İşlevin değerlendirmesi sırasında, çeşitli SkyFunction.Environment.getValue()
aşırı yüklemelerini çağırarak Skyframe'den başka bağımlılıklar talep edebilir. Bu, söz konusu bağımlılıkları Skyframe'in dahili grafiğine kaydetmenin yan etkisine sahiptir ve böylece Skyframe, bağımlılıklarından biri değiştiğinde fonksiyonu yeniden değerlendireceğini bilir. Başka bir deyişle, Skyframe'in önbelleğe alma ve artımlı hesaplaması, SkyFunction
ve SkyValue
ayrıntı düzeyinde çalışır.
SkyFunction
, kullanılamayan bir bağımlılık istediğinde getValue()
, null değerini döndürür. Ardından işlev, kendi başına null değeri döndürerek kontrolü Skyframe'e geri vermelidir. Daha sonra Skyframe, mevcut olmayan bağımlılığı değerlendirir, ardından işlevi baştan yeniden başlatır. Ancak bu sefer getValue()
çağrısı, boş olmayan bir sonuçla başarılı olur.
Bunun sonucunda, yeniden başlatmadan önce SkyFunction
içinde gerçekleştirilen tüm hesaplamaların tekrarlanması gerekir. Ancak önbelleğe alınan SkyValues
bağımlılığını değerlendirmek için yapılan çalışmalar buna dahil değildir. Bu nedenle, genellikle bu sorunu
şu şekilde çözüyoruz:
- Yeniden başlatma sayısını sınırlandırmak için bağımlılıkları toplu olarak bildirme (
getValuesAndExceptions()
kullanarak). SkyValue
, farklıSkyFunction
öğeleri ile hesaplanan ayrı parçalara ayrılır. Böylece bağımsız olarak hesaplanıp önbelleğe alınabilir. Bellek kullanımını artırma potansiyeli olduğu için bu işlem stratejik olarak yapılmalıdır.SkyFunction.Environment.getState()
kullanarak veya "Skyframe'in arkasında" geçici bir statik önbellek tutarak yeniden başlatmalar arasında durumu depolama.
Temelde bu tür geçici çözümlere ihtiyacımız var, çünkü sürekli olarak yayındaki yüz binlerce Skyframe düğümümüz var ve Java hafif iş parçacıklarını desteklemiyor.
Starlark
Starlark, kullanıcıların Bazel'i yapılandırmak ve genişletmek için kullandıkları alana özgü dildir. Python'un kısıtlı bir alt kümesi olarak düşünülür. Bu alt küme çok daha az türe, kontrol akışı üzerinde daha fazla kısıtlamaya ve en önemlisi, eşzamanlı okumalara olanak tanıyan güçlü sabitlik garantilerine sahiptir. Bu, bazı (ancak hepsini değil) kullanıcıları dildeki genel programlama görevlerini tamamlamaya çalışmaktan caydıran bir Turing-complete değildir.
Starlark, net.starlark.java
paketinde uygulandı.
Ayrıca burada bağımsız bir Go uygulaması vardır. Bazel'de kullanılan Java uygulaması şu anda çevirmendir.
Starlark çeşitli bağlamlarda kullanılır. Örneğin:
BUILD
dili. Burada yeni kurallar tanımlanır. Bu bağlamda çalışan Starlark kodu, yalnızcaBUILD
dosyasının içeriğine ve bu dosya tarafından yüklenen.bzl
dosyalarına erişebilir.- Kural tanımları. Yeni kurallar (yeni bir dil desteği gibi) bu şekilde tanımlanır. Bu bağlamda çalışan Starlark kodu, doğrudan bağımlılıkları tarafından sağlanan yapılandırmaya ve verilere erişebilir (bu konuda ileride daha fazla bilgi vereceğiz).
- WORKSPACE dosyası. Harici depolar (ana kaynak ağacında bulunmayan kod) burada tanımlanır.
- Depo kuralı tanımları. Bu aşamada yeni harici depo türleri tanımlanır. Bu bağlamda çalışan Starlark kodu, Bazel'in çalıştığı makinede rastgele kod çalıştırabilir ve çalışma alanının dışına erişebilir.
BUILD
ve .bzl
dosyalarının lehçeleri, farklı şeyleri ifade ettiklerinden biraz farklıdır. Farklılıkların listesini burada bulabilirsiniz.
Starlark hakkında daha fazla bilgiyi burada bulabilirsiniz.
Yükleme/analiz aşaması
Yükleme/analiz aşamasında Bazel, belirli bir kuralı oluşturmak için hangi işlemlerin gerektiğini belirler. Temel birimi, makul bir şekilde bir (hedef, yapılandırma) çifti olan "yapılandırılmış hedef"tir.
Daha önce serileştirilmiş olan iki farklı bölüme ayrılabildiği için bu aşamaya "yükleme/analiz aşaması" denir; ancak bu aşamalar artık zaman içinde çakışabilir:
- Paketleri yükleme, yani
BUILD
dosyalarını bunları temsil edenPackage
nesnelere dönüştürme - Yapılandırılmış hedefleri analiz etmek, yani eylem grafiğini oluşturmak için kuralların uygulanmasını çalıştırmak
Komut satırında istenen yapılandırılmış hedeflerin geçişli kapatma işlemindeki yapılandırılmış her hedef, aşağıdan yukarıya analiz edilmelidir. Başka bir deyişle, önce yaprak düğümleri, ardından komut satırındakilere kadar analiz edilmelidir. Yapılandırılmış tek bir hedefin analizi için yapılan girişler şunlardır:
- Yapılandırma. ("Bu kuralın nasıl oluşturulacağı"; örneğin, hedef platform, kullanıcının C++ derleyicisine iletilmesini istediği komut satırı seçenekleri gibi ayarlar)
- Doğrudan bağımlılıklar. Geçişli bilgi sağlayıcıları, analiz edilen kural için kullanılabilir. Bu şekilde adlandırılırlar çünkü yapılandırılmış hedefin geçişli kapanmasında, sınıf yolundaki tüm .jar dosyaları veya bir C++ ikili programına bağlanması gereken tüm .o dosyaları gibi bilgilerin "toplayıcısı" bulunur.
- Hedefin kendisi. Bu, hedefin içinde bulunduğu paketin yüklenmesiyle sonuçlanır. Kurallar için ise özellikler, genellikle önem teşkil eder.
- Yapılandırılmış hedefin uygulanması. Kurallar için bu, Starlark'ta veya Java'da olabilir. Kuralla yapılandırılmamış tüm hedefler Java'da uygulanır.
Yapılandırılmış bir hedef analizinin sonucu:
- Buna bağlı hedefleri yapılandıran geçişli bilgi sağlayıcılar,
- Oluşturabileceği eserler ve bunları üreten eylemler.
Java kurallarına sunulan API RuleContext
, Starlark kurallarının ctx
bağımsız değişkenine eşdeğerdir. API'si daha güçlüdür, ancak aynı zamanda Bad ThingsTM yapmak daha kolaydır. Örneğin, zamanı veya alan karmaşıklığı ikinci dereceden (veya daha kötüsü) olan bir kod yazmak, Bazel sunucusunun bir Java istisnası ile çökertilmesi veya sabit değerleri ihlal etmek (ör. bir Options
örneğini yanlışlıkla değiştirmek ya da yapılandırılmış bir hedefi değişken yapmak suretiyle) gibi işlemler için kullanabilirsiniz.
Yapılandırılmış bir hedefin doğrudan bağımlılıklarını belirleyen algoritma DependencyResolver.dependentNodeMap()
içinde yayındadır.
Yapılandırmalar
Yapılandırmalar bir hedef oluşturma "nasıl"dır? Örneğin, hangi platform için, hangi komut satırı seçenekleriyle vb.
Bir derlemedeki birden fazla yapılandırma için aynı hedef oluşturulabilir. Bu, örneğin derleme sırasında ve hedef kod için çalıştırılan bir araç için aynı kod kullanıldığında ve çapraz derleme yaparken veya bir fat Android uygulaması (birden fazla CPU mimarisi için yerel kod içeren bir uygulama) geliştirdiğimizde faydalıdır.
Kavramsal olarak yapılandırma bir BuildOptions
örneğidir. Ancak BuildOptions
, uygulamada ek işlevler sağlayan BuildConfiguration
ile sarmalanmıştır. Bağımlılık grafiğinin en üstünden
en alta doğru yayılır. Değişmesi durumunda derlemenin
yeniden analiz edilmesi gerekir.
Bu durum, yalnızca test hedeflerini etkilemesine rağmen, örneğin istenen test çalıştırmalarının sayısı değişirse derlemenin tamamını yeniden analiz etmek zorunda kalmak gibi anormalliklere yol açar (bu durumu olmaması için yapılandırmaları "kırpma" planlarımız mevcuttur ancak henüz hazır değildir).
Bir kural uygulamasının yapılandırmanın bir parçası olması gerektiğinde, bunu RuleClass.Builder.requiresConfigurationFragments()
kullanarak tanımında belirtmesi gerekir. Bunun amacı hem hatalardan kaçınmak (ör. Java parçasını kullanan Python kuralları) hem de Python seçenekleri değiştiğinde C++ hedeflerinin yeniden analiz edilmesine gerek kalmaması için yapılandırma kırpma işlemini kolaylaştırmaktır.
Bir kuralın yapılandırması, "üst" kuralının yapılandırmasıyla aynı değildir. Bağımlılık ucunda yapılandırmayı değiştirme işlemine "yapılandırma geçişi" adı verilir. Bu durum iki yerde gerçekleşebilir:
- Bağımlılık ucunda. Bu geçişler
Attribute.Builder.cfg()
içinde belirtilmiştir ve birRule
(geçişin gerçekleştiği) veBuildOptions
(orijinal yapılandırma) konumundan bir veya daha fazlaBuildOptions
öğesine (çıktı yapılandırması) kullanılan işlevlerdir. - Yapılandırılmış bir hedefe gelen herhangi bir uçta. Bunlar
RuleClass.Builder.cfg()
içinde belirtilmiştir.
İlgili sınıflar: TransitionFactory
ve ConfigurationTransition
.
Yapılandırma geçişleri kullanılır. Örneğin:
- Derleme sırasında belirli bir bağımlılığın kullanıldığını ve dolayısıyla yürütme mimarisinde derlenmesi gerektiğini belirtmek için
- Belirli bir bağımlılığın birden fazla mimari için oluşturulması gerektiğini belirtmek için (şişman Android APK'larındaki yerel kod gibi)
Bir yapılandırma geçişi birden fazla yapılandırmaya yol açıyorsa buna bölünmüş geçiş adı verilir.
Yapılandırma geçişleri Starlark'ta da uygulanabilir (dokümanlara buradan ulaşabilirsiniz)
Geçiş bilgisi sağlayıcıları
Geçiş bilgisi sağlayıcıları, yapılandırılmış hedeflere bağlı diğer yapılandırılmış hedefler hakkında bilgi vermenin bir yoludur (ve _only _way). Adında "geçişli" olmasının nedeni, bunun genellikle bir tür yapılandırılmış hedefin geçişli kapanmasının bir tür toplamı olmasıdır.
Java geçişli bilgi sağlayıcıları ile Starlark sağlayıcıları arasında genellikle bire bir yazışma olur (Bu API'nin doğrudan Java’nın harf çevirisinden daha Starlark'a benzemesi nedeniyle
FileProvider
, FilesToRunProvider
ve RunfilesProvider
öğelerinin birleşiminden oluşan DefaultInfo
istisnadır).
Bunların temeli aşağıdakilerden biridir:
- Java Sınıfı nesnesi. Bu özellik yalnızca Starlark'tan erişilemeyen sağlayıcılar tarafından kullanılabilir. Bu sağlayıcılar
TransitiveInfoProvider
alt sınıfıdır. - Dizedir. Bu, eskidir ve ad çakışmasına açık olduğu için
kesinlikle önerilmez. Bu tür geçişli bilgi sağlayıcılar,
build.lib.packages.Info
öğesinin doğrudan alt sınıflarıdır . - Sağlayıcı sembolü. Bu,
provider()
işlevi kullanılarak Starlark'tan oluşturulabilir ve yeni sağlayıcı oluşturmak için önerilen yöntemdir. Sembol, Java'da birProvider.Key
örneğiyle temsil edilir.
Java'da uygulanan yeni sağlayıcılar BuiltinProvider
kullanılarak uygulanmalıdır.
NativeProvider
desteği sonlandırıldı (henüz kaldıracak vaktimiz yoktu) ve
TransitiveInfoProvider
alt sınıfa Starlark'tan erişilemiyor.
Yapılandırılmış hedefler
Yapılandırılmış hedefler RuleConfiguredTargetFactory
olarak uygulanır. Java'da uygulanan her kural sınıfı için bir alt sınıf bulunur. Starlark tarafından yapılandırılan hedefler
StarlarkRuleConfiguredTargetUtil.buildRule()
üzerinden oluşturulur .
Yapılandırılmış hedef fabrikalar, dönüş değerlerini oluşturmak için RuleConfiguredTargetBuilder
kullanmalıdır. Şunlardan oluşur:
filesToBuild
, "bu kuralın temsil ettiği dosya kümesi"ne ilişkin belirsiz bir kavramdır. Bunlar, yapılandırılmış hedef, komut satırında veya bir Genrule dokümanının src'lerinde olduğunda oluşturulan dosyalardır.- Çalıştırma dosyaları, normal ve veriler.
- Çıkış grupları. Bunlar, kuralın oluşturabileceği çeşitli "diğer dosya gruplarıdır". Bunlara, BUILD içindeki dosya grubu kuralının exit_group özelliği ve Java'da
OutputGroupInfo
sağlayıcısı kullanılarak erişilebilir.
Çalıştırma dosyaları
Bazı ikili programların çalışması için veri dosyaları gerekir. Bunun belirgin bir örneği, giriş dosyaları gerektiren testlerdir. Bu, Bazel'de "runfiles" kavramıyla temsil edilir. "runfiles ağacı", belirli bir ikili program için veri dosyalarının dizin ağacıdır. Dosya sisteminde, çıkış ağaçlarının kaynağındaki dosyalara işaret eden bağımsız sembolik bağlantılara sahip bir sembolik bağlantı ağacı olarak oluşturulur.
Çalıştırmalar grubu, Runfiles
örneği olarak temsil edilir. Kavramsal olarak bir dosyanın çalışma dosyaları ağacındaki yolundan, dosyayı temsil eden Artifact
örneğine uzanan bir haritadır. İki nedenden dolayı tek bir Map
parametresinden biraz daha karmaşıktır:
- Çoğu zaman, bir dosyanın runfiles yolu, execpath ile aynıdır. Bunu, RAM'den tasarruf etmek için kullanırız.
- Runfiles ağaçlarında, bunların da temsil edilmesi gereken çeşitli eski giriş türleri bulunur.
Çalışma dosyaları RunfilesProvider
kullanılarak toplanır: Bu sınıfın bir örneği, yapılandırılmış bir hedefi (kitaplık gibi) ve onun geçişli kapatma ihtiyaçlarını temsil eder ve iç içe yerleştirilmiş bir küme gibi toplanır (aslında, kapak altındaki iç içe yerleştirilmiş kümeler kullanılarak uygulanırlar): Her hedef, bağımlılarının çalıştırma dosyalarını birleştirir, kendilerinden bir kısmını ekler ve ardından, elde edilen kurulumu bağımlılık grafiğinde gönderir. Bir RunfilesProvider
örneği, biri kuralın "data" özelliği üzerinden bağımlı olduğu durumlar için, diğeri de gelen bağımlılık türleri için olmak üzere iki Runfiles
örneği içerir. Bunun nedeni, bir hedefin bir veri özelliğine bağlı olduğunda bazen diğerlerinden farklı çalıştırma dosyaları sunmasıdır. Bu, henüz kaldırmadığımız, istenmeyen eski davranıştır.
İkili programların çalıştırma dosyaları, RunfilesSupport
örneği olarak gösterilir. RunfilesSupport
aslında oluşturma yeteneğine sahip olduğundan (yalnızca eşleme olan Runfiles
'den farklı olarak) bu, Runfiles
ürününden farklıdır. Bu işlem için aşağıdaki ek bileşenler gerekir:
- Runfiles manifesti. Bu, Runfiles ağacının serileştirilmiş bir açıklamasıdır. Bu dosya, runfiles ağacının içerikleri için bir proxy olarak kullanılır ve Bazel, yalnızca manifestin içeriği değiştiğinde çalıştırma dosyaları ağacının değiştiğini varsayar.
- Çıkış runfiles manifesti. Bu, özellikle de bazen sembolik bağlantıları desteklemeyen Windows'da, çalışma dosyası ağaçlarını işleyen çalışma zamanı kitaplıkları tarafından kullanılır.
- Runfiles arabulucu. Runfiles ağacının var olması için sembolik bağlantı ağacını ve sembolik bağlantıların işaret ettiği yapıyı derlemeniz gerekir. Bağımlılık kenarlarının sayısını azaltmak için, bunların tümünü temsil etmek üzere runfiles ara yöneticisi kullanılabilir.
- Çalışma dosyalarını
RunfilesSupport
nesnesinin temsil ettiği ikili programı çalıştırmak için komut satırı bağımsız değişkenleri.
Yönler
Oranlar, "hesaplamayı bağımlılık grafiğinde yaymanın" bir yoludur. Bu özellikler, Bazel kullanıcıları için burada açıklanmıştır. Protokol arabellekleri iyi bir motive edici örnektir: proto_library
kuralı herhangi bir dili bilmemelidir. Ancak herhangi bir programlama dilinde protokol arabellek mesajının ("protokol arabelleklerinin "temel birimi") uygulanmasını oluşturmak, proto_library
kuralına bağlanmalıdır. Böylece aynı dildeki iki hedef aynı protokol arabelleğine bağlıysa mesaj yalnızca bir kez derlenir.
Yapılandırılmış hedefler gibi, Skyframe'de de SkyValue
olarak temsil edilir ve oluşturulma şekilleri, yapılandırılmış hedeflerin oluşturulma biçimlerine çok benzer: RuleContext
'ye erişimi olan ConfiguredAspectFactory
adında bir fabrika sınıfı vardır. Ancak yapılandırılmış hedef fabrikaların aksine, ekli olduğu yapılandırılmış hedef ve sağlayıcılarını da bilir.
Bağımlılık grafiğinde aşağı yayılan yönler, her özellik için Attribute.Builder.aspects()
işlevi kullanılarak belirtilir. Sürece katılan, aynı ismi taşıyan birkaç sınıf vardır:
AspectClass
, özelliğin uygulanmasıdır. Java'da (bu durumda bir alt sınıftır) veya Starlark'ta (bu durumdaStarlarkAspectClass
öğesinin bir örneğidir) olabilir.RuleConfiguredTargetFactory
koduna benzer.AspectDefinition
, özelliğin tanımıdır. Gerektiği sağlayıcıları ve sağladığı sağlayıcıları içerir ve uygulamanın uygulanması için uygunAspectClass
örneği gibi bir referans içerir.RuleClass
ile benzerdir.AspectParameters
, bağımlılık grafiğinde yayılan bir unsuru parametriye etmenin bir yoludur. Şu anda dize eşleme olarak kullanılıyor. Neden yararlı olduğuna dair iyi bir örnek de protokol arabellekleridir: Bir dilde birden fazla API varsa protokol arabelleklerinin hangi API için oluşturulması gerektiğiyle ilgili bilgiler bağımlılık grafiğinde aşağı doğru dağıtılmalıdır.Aspect
, bağımlılık grafiğinde yayan bir unsuru hesaplamak için gereken tüm verileri temsil eder. En boy sınıfı, tanımı ve parametrelerinden oluşur.RuleAspect
, belirli bir kuralın hangi yönlerin yayılması gerektiğini belirleyen işlevdir. Bu,Rule
->Aspect
fonksiyonudur.
Biraz beklenmedik bir karışıklık, bazı yönlerin başka yönlere de eklenebiliyor olmasıdır. Örneğin, bir Java IDE için sınıf yolunu toplayan bir özellik muhtemelen sınıf yolundaki tüm .jar dosyaları hakkında bilgi almak isteyecektir, ancak bunlardan bazıları protokol arabellekleridir. Bu durumda, IDE özelliği (proto_library
kuralı + Java proto yönü) çiftine eklenmelidir.
Belirli yönlerin karmaşıklığını bu sınıfta ele alıyoruz
AspectCollection
.
Platformlar ve araç zincirleri
Bazel, derleme işlemlerinin çalıştırıldığı birden fazla mimarinin ve kodun derlendiği birden fazla mimarinin bulunabileceği derlemeleri, yani çoklu platform derlemelerini destekler. Bu mimariler Bazel dilinde platformlar olarak adlandırılır (tüm belgelere buradan ulaşabilirsiniz)
Platformlar, kısıtlama ayarlarından ("CPU mimarisi" kavramı gibi), kısıtlı değerlere (ör. x86_64 gibi belirli bir CPU) giden bir anahtar/değer eşlemesi ile tanımlanır. @platforms
deposunda, en sık kullanılan kısıtlama ayarlarını ve değerlerini içeren bir "sözlüğü" bulabilirsiniz.
Araç zinciri kavramı, derlemenin hangi platformlarda çalıştığına ve hangi platformların hedeflendiğine bağlı olarak farklı derleyiciler kullanılmasının gerekebilmesinden kaynaklanır. Örneğin, belirli bir C++ araç zinciri belirli bir işletim sisteminde çalışabilir ve başka işletim sistemlerini hedefleyebilir. Bazel, belirlenen yürütmeye ve hedef platforma göre kullanılan C++ derleyiciyi belirlemelidir (araç zincirleri için belgeler burada).
Bunu yapmak için araç zincirlerine, destekledikleri yürütme ve hedef platform kısıtlamalarıyla not verilir. Bunu yapabilmek için araç zincirinin tanımı iki bölüme ayrılır:
- Yürütme ve hedef kısıtlamalarını açıklayan
toolchain()
kuralı, bir araç zincirinin desteklediğini, ne tür araç zinciri olduğunu (C++ veya Java gibi) belirtir (ikincisi,toolchain_type()
kuralıyla temsil edilir). - Gerçek araç zincirini açıklayan, dile özgü bir kural (
cc_toolchain()
gibi)
Bu şekilde yapılır çünkü araç zinciri çözümlemesi yapabilmek için her araç zincirinin kısıtlamalarını bilmemiz gerekir. Dile özgü *_toolchain()
kuralları da bundan çok daha fazla bilgi içerdiğinden yüklenmeleri daha uzun sürer.
Yürütme platformları aşağıdaki yollardan biriyle belirtilir:
register_execution_platforms()
işlevi kullanılarak WORKSPACE dosyasında- Komut satırında --extra_execution_platforms komut satırı seçeneğini kullanarak
Mevcut yürütme platformları grubu RegisteredExecutionPlatformsFunction
cinsinden hesaplanır .
Yapılandırılmış bir hedefin hedef platformu, PlatformOptions.computeTargetPlatform()
ile belirlenir . Nihayetinde birden çok hedef platformu desteklemek istesek de bu liste henüz uygulanmadığı için bir platformlar listesi oluşturuyor.
Yapılandırılmış bir hedef için kullanılacak araç zincirleri grubu ToolchainResolutionFunction
tarafından belirlenir. İşlevi:
- Kayıtlı araç zinciri grubu (WORKSPACE dosyasında ve yapılandırmada)
- İstenen yürütme ve hedef platformlar (yapılandırmada)
- Yapılandırılmış hedefin gerektirdiği araç zinciri türleri grubu (
UnloadedToolchainContextKey)
içinde UnloadedToolchainContextKey
içindeki yapılandırılmış hedefin (exec_compatible_with
özelliği) ve yapılandırmanın (--experimental_add_exec_constraints_to_targets
) yürütme platformu kısıtlamaları grubu
Bunun sonucunda UnloadedToolchainContext
bulunur. Bu, temelde araç zinciri türünden (ToolchainTypeInfo
örneği olarak temsil edilir) seçili araç zincirinin etiketine kadar olan bir haritadır. Araç zincirlerinin kendisini değil, yalnızca etiketlerini içermesi nedeniyle "unload" olarak adlandırılmıştır.
Daha sonra araç zincirleri aslında ResolvedToolchainContext.load()
kullanılarak yüklenir ve bunları isteyen yapılandırılmış hedefin uygulanması tarafından kullanılır.
Ayrıca, tek bir "ana makine" yapılandırması olmasını ve --cpu
gibi çeşitli yapılandırma bayraklarıyla temsil edilen hedef yapılandırmalarını temel alan eski bir sistemimiz de vardır . Kademeli olarak yukarıdaki sisteme
geçiyoruz. Kullanıcıların eski yapılandırma değerlerini kullandığı durumları ele almak için eski flag'ler ile yeni stil platform kısıtlamaları arasında geçiş yapmak için platform eşlemeleri uyguladık.
Kodu PlatformMappingFunction
dilindedir ve Starlark dışında bir "küçük dil"
kullanmaktadır.
Sınırlamalar
Bazen kullanıcılar bir hedefi yalnızca birkaç platformla uyumlu olarak tanımlamak isteyebilir. Maalesef Bazel bu amaca ulaşmak için birden fazla mekanizmaya sahiptir:
- Kurala özgü kısıtlamalar
environment_group()
/environment()
- Platform kısıtlamaları
Kurala özgü kısıtlamalar, çoğunlukla Java kuralları için Google'da kullanılır. Kullanımdan kaldırılmaktadırlar ve Bazel'de kullanılamazlar, ancak kaynak kodunda buna referanslar olabilir. Bunu yöneten özelliğe constraints=
denir .
media_group() veenvironment()
Bu kurallar eski bir mekanizmadır ve yaygın olarak kullanılmamaktadır.
Tüm derleme kuralları hangi "ortamlar" için oluşturulabileceğini bildirebilir. Burada "ortam", environment()
kuralının bir örneğidir.
Bir kural için desteklenen ortamlar çeşitli şekillerde belirtilebilir:
restricted_to=
özelliği aracılığıyla. En doğrudan kullanılan spesifikasyon biçimi budur. Kuralın bu grup için desteklediği ortam grubunu tam olarak belirtir.compatible_with=
özelliği aracılığıyla. Bu şekilde, bir kuralın desteklediği, varsayılan olarak desteklenen "standart" ortamlara ek olarak ortamlar tanımlanır.- Paket düzeyindeki
default_restricted_to=
vedefault_compatible_with=
özellikleri aracılığıyla environment_group()
kurallarındaki varsayılan spesifikasyonlar aracılığıyla. Her ortam, tematik olarak birbiriyle ilişkili bir gruba aittir ("CPU mimarileri", "JDK sürümleri" veya "mobil işletim sistemleri"). Ortam grubunun tanımı, bu ortamlardan hangilerininrestricted_to=
/environment()
özellikleriyle aksi belirtilmediği takdirde "varsayılan" tarafından desteklenmesi gerektiğini içerir. Bu tür özelliklere sahip olmayan bir kural, tüm varsayılanları devralır.- Kural sınıfı varsayılanı aracılığıyla. Bu, belirli bir kural sınıfının tüm örnekleri için genel varsayılanları geçersiz kılar. Bu, örneğin her bir örneğin bu özelliği açıkça beyan etmek zorunda kalmadan tüm
*_test
kurallarını test edilebilir hale getirmek için kullanılabilir.
environment()
normal bir kural olarak uygulanırken environment_group()
, Target
öğesinin bir alt sınıfıdır ancak Rule
(EnvironmentGroup
) değildir ve sonunda Starlark'tan (StarlarkLibrary.environmentGroup()
) varsayılan olarak kullanılabilen ve sonunda aynı hedefe sahip bir hedef oluşturur. Bunun nedeni, her ortamın ait olduğu ortam grubunu, her ortam grubunun kendi varsayılan ortamlarını bildirmesi gerektiği için ortaya çıkacak döngüsel bağımlılığı önlemektir.
Derleme, --target_environment
komut satırı seçeneğiyle belirli bir ortamla sınırlandırılabilir.
Kısıtlama kontrolünün uygulanması RuleContextConstraintSemantics
ve TopLevelConstraintSemantics
şeklindedir.
Platform kısıtlamaları
Bir hedefin hangi platformlarla uyumlu olduğunu açıklamanın şu anki "resmi" yolu, araç zincirlerini ve platformları tanımlamak için kullanılan kısıtlamaların aynılarını kullanmaktır. Şu pull isteğinde inceleniyor: #10945.
Görünürlük
Çok sayıda geliştiriciyle (ör. Google'da) büyük bir kod tabanı üzerinde çalışıyorsanız herkesin kodunuza bağlı olarak rastgele şekilde davranmasını önlemeye özen göstermeniz gerekir. Aksi takdirde, Hyrum yasası uyarınca kullanıcılar uygulama ayrıntıları olarak kabul ettiğiniz davranışlar kullanmaya başlar.
Bazel bunu görünürlük adı verilen mekanizmayla destekler: Belirli bir hedefe yalnızca görünürlük özelliğini kullanarak bağımlı olabileceğini belirtebilirsiniz. Bu özellik biraz özeldir çünkü etiket listesi içermesine rağmen bu etiketler, belirli bir hedefe işaret eden işaretçi yerine paket adları üzerinde bir kalıp kodlayabilir. (Evet, bu bir tasarım hatasıdır.)
Bu, aşağıdaki yerlerde uygulanır:
RuleVisibility
arayüzü, görünürlük bildirimini temsil eder. Sabit (tamamen herkese açık veya tamamen gizli) veya bir etiket listesi olabilir.- Etiketler, paket gruplarına (önceden tanımlanmış paket listesi) doğrudan paketlere (
//pkg:__pkg__
) veya paketlerin alt ağaçlarına (//pkg:__subpackages__
) başvurabilir. Bu,//pkg:*
veya//pkg/...
kullanan komut satırı söz diziminden farklıdır. - Paket grupları kendi hedefleri (
PackageGroup
) ve yapılandırılmış hedef (PackageGroupConfiguredTarget
) olarak uygulanır. İstediğimizde bunları basit kurallarla değiştirebiliriz. Bu üçlünün mantığı,//pkg/...
gibi tek bir kalıpa karşılık gelenPackageSpecification
; tek birpackage_group
packages
özelliğine karşılık gelenPackageGroupContents
ve birpackage_group
ile geçişliincludes
üzerinde toplananPackageSpecificationProvider
aracılığıyla uygulanır. - Görünürlük etiketi listelerinden bağımlılıklara dönüşüm,
DependencyResolver.visitTargetVisibility
ve başka birkaç farklı yerde gerçekleştirilir. - Asıl kontrol
CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()
içinde yapılır
İç içe yerleştirilmiş setler
Yapılandırılmış bir hedef, çoğu zaman bağımlılıklarından bir dizi dosya toplar, kendi dosyasını ekler ve toplu kümeyi, buna bağımlı olan yapılandırılmış hedeflerin aynısını yapabilmesi için bir geçişli bilgi sağlayıcıya sarmalar. Örnekler:
- Derleme için kullanılan C++ başlık dosyaları
cc_library
öğesinin geçişli kapatılmasını temsil eden nesne dosyaları- Bir Java kuralının derlemesi veya çalıştırması için sınıf yolunda olması gereken .jar dosyaları
- Python kuralının geçişli kapanışındaki Python dosyaları kümesi
Örneğin, List
veya Set
gibi araçları kullanarak bunu naif bir yolla yaparsak ikinci dereceden bellek kullanımı elde ederiz: N kural zinciri varsa ve her kural bir dosya eklerse koleksiyon üyemiz 1+2+...+N olur.
Bu sorunu çözmek için NestedSet
kavramını ortaya çıkardık. Diğer NestedSet
örneklerinden ve kendi bazı üyelerinden oluşan bir veri yapısıdır. Böylece, kümelerin yönlendirilmiş döngüsel bir grafiği oluşturulur. Bu kurallar değiştirilemez ve üyeleri için iterasyon yapılabilir. Birden fazla yineleme sırası (NestedSet.Order
) tanımlarız: ön sipariş, son sipariş, topolojik (bir düğüm her zaman üst öğelerinden sonra gelir) ve "önemli değil ama her seferinde aynı olması gerekir".
Aynı veri yapısı, Starlark'ta depset
olarak adlandırılır.
Yapılar ve İşlemler
Gerçek derleme, kullanıcının istediği çıkışı üretmek için çalıştırılması gereken bir dizi komuttan oluşur. Komutlar Action
sınıfının örnekleri olarak, dosyalar ise Artifact
sınıfının örnekleri olarak sunulur. Bunlar "eylem grafiği" adı verilen iki taraflı, yönlendirilmiş, döngüsel bir grafik halinde düzenlenir.
Yapılar iki türdedir: kaynak yapılar (Bazel yürütmeye başlamadan önce kullanılabilenler) ve türetilen yapılar (derlenmesi gerekenler). Türetilmiş yapılar birden çok tür olabilir:
- **Normal yapılar. **Bunlar, sağlama toplamları hesaplanarak güncelliği kontrol edilir ve kısayol olarak mtime kullanılır. Saat değişmemişse dosyayı sağlamayız.
- Çözümlenmemiş sembolik bağlantı yapıları. Bunlar, readlink() çağrısı yapılarak güncellik kontrol edilir. Normal yapıların aksine, bunlar sarmal sembolik bağlantılar olabilir. Genellikle, bir kişinin bazı dosyaları bir tür arşiv halinde topladığı durumlarda kullanılır.
- Ağaç yapıları. Bunlar tek dosyalar değil, dizin ağaçlarıdır. Dosyalardaki dosya grubu ve içerikleri kontrol edilerek bu dosyaların güncelliği kontrol edilir. Bunlar
TreeArtifact
olarak gösterilir. - Sabit meta veri yapıları. Bu yapılarda yapılan değişiklikler, yeniden derlemeyi tetiklemez. Bu, yalnızca derleme damgası bilgileri için kullanılır. Sırf geçerli zaman değişti diye bir yeniden oluşturma yapmak istemeyiz.
Kaynak yapıların ağaç yapıları veya çözülmemiş sembolik bağlantı yapıları olmamasının temel bir nedeni yoktur. Sadece bu yapıyı henüz uygulamamışızdır (yine de BUILD
dosyasında kaynak dizine referans vermek, Bazel'da bilinen ve uzun zamandır süregelen az sayıdaki hatadan biridir. BAZEL_TRACK_SOURCE_DIRECTORIES=1
JVM mülkü tarafından etkinleştirilen bu tür bir uygulamamız vardır.)
Kayda değer bir Artifact
türü aracıdır. Bunlar, MiddlemanAction
çıktıları olan Artifact
örnekle gösterilir. Bunlar bazı şeyleri özel olarak
durumda kullanmak için kullanılır:
- Toplanan aracılar, yapıları birlikte gruplandırmak için kullanılır. Böylece, birçok işlem aynı büyük giriş grubunu kullanıyorsa N*M bağımlılık kenarlarımız değil, yalnızca N+M (iç içe yerleştirilmiş kümelerle değiştirilir) bağımlılık kenarlarımız vardır.
- Bağımlılık aracılarını programlamak, bir işlemin diğerinden önce çalışmasını sağlar.
Bunlar, çoğunlukla hata analizi için değil, aynı zamanda C++ derlemesi için de kullanılır (açıklama için
CcCompilationContext.createMiddleman()
adresini ziyaret edin). - Runfiles aracıları, bir runfiles ağacının bulunduğundan emin olmak için kullanılır. Böylece, ayrı olarak çıkış manifestine ve runfiles ağacının başvuruda bulunduğu her bir yapıya bağımlı olması gerekmez.
İşlemler; çalıştırılması gereken bir komut, ihtiyaç duyulan ortam ve ürettiği çıkış kümesi olarak en iyi şekilde anlaşılabilir. Bir işlem açıklamasının ana bileşenleri şunlardır:
- Çalıştırılması gereken komut satırı
- İhtiyaç duyduğu giriş yapıları
- Ayarlanması gereken ortam değişkenleri
- Çalışması gereken ortamı (ör. platform) açıklayan notlar \
İçeriği Bazel'in bildiği bir dosya yazmak gibi başka özel durumlar da vardır. Bunlar, AbstractAction
dersinin alt sınıfıdır. Java ve C++'ın kendi işlem türleri (JavaCompileAction
, CppCompileAction
ve CppLinkAction
) olsa da işlemlerin çoğu SpawnAction
veya StarlarkAction
'dır (aynı şekilde, muhtemelen ayrı sınıflar da olmamalıdır).
Sonunda her şeyi SpawnAction
öğesine taşımak istiyoruz. JavaCompileAction
oldukça yakındır ancak C++, .d dosyası ayrıştırması ve taramayı içermesi nedeniyle biraz özel bir kullanımdır.
Eylem grafiği, genellikle Skyframe grafiğine "yerleştirilmiştir": Kavramsal olarak, bir işlemin yürütülmesi ActionExecutionFunction
çağrısı olarak gösterilir. Bir işlem grafiği bağımlılık kenarından Skyframe bağımlılık kenarına eşleme, ActionExecutionFunction.getInputDeps()
ve Artifact.key()
içinde açıklanmıştır ve Skyframe kenarlarının sayısını düşük tutmak için birkaç optimizasyon vardır:
- Türetilen yapıların kendi
SkyValue
'leri yoktur. Bunun yerine, işlemi oluşturan işlemin anahtarını bulmak içinArtifact.getGeneratingActionKey()
kullanılır - İç içe yerleştirilmiş setlerin kendi Skyframe anahtarı vardır.
Paylaşılan işlemler
Bazı eylemler, yapılandırılmış birden fazla hedef tarafından oluşturulur. Starlark kuralları, türetilen işlemlerini yalnızca yapılandırmaları ve paketleri tarafından belirlenen bir dizine yerleştirmelerine izin verilir. Ancak aynı paketteki kurallar çakışabilir ancak Java'da uygulanan kurallar, türetilmiş yapıları herhangi bir yere yerleştirebilir.
Bunun yanlış bir özellik olduğu düşünülse de bundan kurtulmak gerçekten zordur. Bunun nedeni, örneğin bir kaynak dosyasının bir şekilde işlenmesi gerektiği ve bu dosyaya birden fazla kural (el dalgası el dalgası) tarafından referans verilmesi gerektiği gibi durumlarda yürütme süresinde önemli tasarruf sağlamasıdır. Bu işlem için bir miktar RAM ödenmesi gerekir: Paylaşılan eylemin her bir örneğinin bellekte ayrı olarak depolanması gerekir.
İki işlem aynı çıkış dosyasını oluşturuyorsa tamamen aynı olmalıdır: Aynı girişlere, aynı çıkışlara sahip olmalı ve aynı komut satırını çalıştırmalıdır. Bu denklik ilişkisi Actions.canBeShared()
içinde uygulanır ve analiz ile yürütme aşamaları arasında her İşleme bakılarak doğrulanır.
Bu, SkyframeActionExecutor.findAndStoreArtifactConflicts()
ürününde uygulanmıştır ve Bazel'de derleme için "global" görünüm gerektiren az sayıda yerden biridir.
Yürütme aşaması
Bu noktada Bazel, çıkış üreten komutlar gibi derleme işlemlerini çalıştırmaya başlar.
Analiz aşamasından sonra Bazel'in yaptığı ilk şey, hangi yapıların oluşturulması gerektiğini belirlemektir. Bunun mantığı TopLevelArtifactHelper
ile kodlanmıştır. Özetle, komut satırındaki yapılandırılmış hedeflerin filesToBuild
değeri ve özel çıkış grubunun içeriği, "bu hedef komut satırındaysa bu yapıları oluştur" ifadesiyle açıkça belirtilir.
Bir sonraki adım, yürütme kökünü oluşturmaktır. Bazel, dosya sistemindeki farklı konumlardan kaynak paketleri (--package_path
) okuma seçeneğine sahip olduğu için tam kaynak ağacıyla yerel olarak gerçekleştirilen işlemler sağlamalıdır. Bu, SymlinkForest
sınıfı tarafından yönetilir ve analiz aşamasında kullanılan her hedefi not alarak ve her paketi, gerçek konumundan kullanılan bir hedefle sembolize eden tek bir dizin ağacı oluşturarak çalışır. Alternatif bir yöntem de, komutlara doğru yolları iletmektir (--package_path
dikkate alınarak).
Bu istenmeyen bir durumdur çünkü:
- Bir paket bir paket yolu girişinden diğerine taşındığında işlem komut satırlarını değiştirir (eskiden yaygın olarak karşılaşılan bir durumdu)
- Bir işlem uzaktan çalıştırılırsa yerel olarak çalıştırıldığında farklı komut satırları ortaya çıkar
- Kullanılan araca özel bir komut satırı dönüşümü gerektirir (Java sınıf yolları ile C++ dahil etme yolları arasındaki farkı göz önünde bulundurun).
- Bir işlemin komut satırı değiştirildiğinde işlem önbelleği girişi geçersiz kılınır
--package_path
yavaş yavaş ve sürekli olarak kullanımdan kaldırılıyor
Ardından Bazel, eylem grafiğinde (eylemlerden ve bunların giriş ve çıkış yapılarından oluşan iki partili, yönlendirilmiş grafik) gezinmeye ve işlemleri çalıştırmaya başlar.
Her bir işlemin yürütülmesi SkyValue
sınıfının ActionExecutionValue
bir örneğiyle gösterilir.
Bir işlemi çalıştırmak pahalı olduğundan, Skyframe'in arkasından vurulabilecek birkaç önbellek katmanımız vardır:
ActionExecutionFunction.stateMap
, Skyframe'inActionExecutionFunction
yeniden başlatmasını ucuz hale getirecek veriler içeriyor- Yerel işlem önbelleği, dosya sisteminin durumuyla ilgili verileri içerir
- Uzaktan yürütme sistemleri genellikle kendi önbelleklerini de içerir.
Yerel işlem önbelleği
Bu önbellek, Skyframe'in arkasında bulunan başka bir katmandır. Bir işlem Skyframe'de yeniden yürütülse bile yerel işlem önbelleğinde isabet olabilir. Yerel dosya sisteminin durumunu temsil eder ve diske serileştirilir. Bu, yeni bir Bazel sunucusu başlatıldığında Skyframe grafiği boş olsa bile yerel işlem önbelleği isabetlerinin alınabileceği anlamına gelir.
Bu önbellek, ActionCacheChecker.getTokenIfNeedToExecute()
yöntemi kullanılarak isabetler açısından kontrol edilir .
Adının aksine, türetilmiş bir eserin izlediği yoldan onu oluşturan eyleme kadar uzanan bir haritadır. İşlem şu şekilde açıklanır:
- Giriş ve çıkış dosyaları ile bunların sağlama toplamı
- Genellikle yürütülen ancak genel olarak yürütülen komut satırı olan "işlem anahtarı", giriş dosyalarının sağlaması tarafından yakalanmayan her şeyi temsil eder (örneğin
FileWriteAction
için yazılan verilerin sağlama toplamıdır).
Ayrıca hâlâ geliştirme aşamasında olan son derece deneysel bir "yukarıdan aşağıya işlem önbelleği" vardır. Bu önbellek, önbelleğe çok fazla gitmekten kaçınmak için geçişli karmalardan yararlanır.
Giriş keşfi ve giriş ayıklama
Bazı işlemler sadece bir dizi girdiye sahip olmaktan daha karmaşıktır. Bir işlemin girdi grubunda yapılan değişiklikler iki şekilde olur:
- Bir eylem, yürütmeden önce yeni girişler keşfedebilir veya bazı girişlerinin aslında gerekli olmadığına karar verebilir. Standart örnek C++'dır. Bu yöntemde, her dosyayı uzak yöneticilere göndermek zorunda
kalmamak için bir C++ dosyasının hangi başlık dosyalarını
kullanacağı konusunda bilgiye dayalı bir tahminde bulunmak daha iyidir.
Bu nedenle, her başlık dosyasını "giriş" olarak kaydetmeme seçeneğimiz vardır. Şu anda, kaynak dosyayı
geçişli olarak
hazırlamak için sadece bu seçenekteki dosyaları fazla tahmin etmek istemiyoruz.
Bu nedenle, yalnızca bu seçenekteki
öğeler için tam tahmine ihtiyaç duymuyoruz.
#include
- Bir işlem, yürütülürken bazı dosyaların kullanılmadığını fark edebilir. C++ ürününde buna ".d dosyaları" adı verilir. Derleyici, olaydan sonra hangi başlık dosyalarının kullanıldığını söyler. Bazel, Maket'ten daha kötü bir artışın utanç duyma ihtimalini ortadan kaldırmak için bu bilgiden yararlanır. Bu yöntem, derleyiciye dayandığından ekleme tarayıcısından daha iyi bir tahmin sunar.
Bunlar, İşlem yöntemleri kullanılarak uygulanır:
Action.discoverInputs()
çağrıldı. Zorunlu olduğu belirlenen iç içe yerleştirilmiş bir yapı grubunu döndürür. Bunların kaynak yapılar olması gerekir. Böylece, eylem grafiğinde yapılandırılan hedef grafikte eşdeğeri olmayan bağımlılık kenarları olmaz.- İşlem,
Action.execute()
çağrısı yapılarak gerçekleştirilir. Action.execute()
programının sonunda işlemAction.updateInputs()
öğesini çağırarak Bazel'a tüm girişlerine ihtiyaç duyulmadığını söyleyebilir. Kullanılan bir girişin kullanılmadığı bildirilirse bu durum yanlış artımlı derlemelere neden olabilir.
İşlem önbelleği yeni bir Action örneğinde (ör. sunucu yeniden başlatıldıktan sonra oluşturulan) isabet döndürdüğünde Bazel, giriş grubunun daha önce yapılan giriş keşfi ve budama işleminin sonucunu yansıtması için updateInputs()
işlevini çağırır.
Starlark işlemleri, bu özellikten yararlanarak bazı girişleri "kullanılmıyor" olarak belirtebilir. Bunun için ctx.actions.run()
öğesinin unused_inputs_list=
bağımsız değişkenini kullanabilir.
İşlem yürütmenin çeşitli yolları: Stratejiler/ActionContexts
Bazı işlemler farklı şekillerde çalıştırılabilir. Örneğin, bir komut satırı yerel olarak, yerel olarak ancak çeşitli korumalı alanlarda veya uzaktan yürütülebilir. Bunu içeren kavrama ActionContext
(veya yeniden adlandırma işleminin sadece yarısını başarıyla tamamladığımız için Strategy
...) denir.
Bir işlem bağlamının yaşam döngüsü aşağıdaki gibidir:
- Yürütme aşaması başladığında
BlazeModule
örneğe hangi işlem bağlamlarına sahip oldukları sorulur. Bu,ExecutionTool
oluşturucuda gerçekleşir. İşlem bağlamı türleri,ActionContext
alt arayüzünü ifade eden ve işlem bağlamının uygulaması gereken arayüzü ifade eden JavaClass
örneğiyle tanımlanır. - Uygun işlem bağlamı, mevcut olanlar arasından seçilir ve
ActionExecutionContext
veBlazeExecutor
özelliklerine yönlendirilir . - İşlemler,
ActionExecutionContext.getContext()
veBlazeExecutor.getStrategy()
kullanarak bağlam ister (bunu yapmanın tek bir yolu vardır...)
Stratejiler, görevlerini yerine getirmek için başka stratejilerden yararlanabilir. Bu, örneğin eylemleri hem yerel hem de uzaktan başlatan dinamik stratejide, daha sonra hangisinin önce bittiğinin kullanıldığı dinamik stratejide kullanılır.
Kayda değer bir strateji, kalıcı çalışan süreçlerini uygulayan uygulamadır (WorkerSpawnStrategy
). Bazı araçlar uzun başlatma süresine sahiptir ve bu nedenle her işlem için yeni bir işlem yapmak yerine işlemler arasında tekrar kullanılması gerekir (Bazel, çalışan sürecinin ayrı ayrı istekler arasında gözlemlenebilir durum taşımaması vaadinde bulunduğu için bu potansiyel bir doğruluk sorunudur).
Araç değişirse çalışan işleminin yeniden başlatılması gerekir. Bir çalışanın yeniden kullanılıp kullanılamayacağı, WorkerFilesHash
kullanılarak kullanılan araç için bir sağlama toplamı hesaplanarak belirlenir. Hangi eylem girişlerinin aracın bir parçasını ve hangilerinin girişleri temsil ettiğini bilmek önemlidir. Bu, İşlemi oluşturan kişi (Spawn.getToolFiles()
) tarafından belirlenir ve Spawn
çalıştırma dosyaları aracın bir parçası olarak sayılır.
Stratejiler (veya işlem bağlamları) hakkında daha fazla bilgi edinin:
- İşlem çalıştırmaya yönelik çeşitli stratejilerle ilgili bilgileri burada bulabilirsiniz.
- Hangisinin önce tamamlandığını görmek için hem yerel hem de uzaktan işlem gerçekleştirdiğimiz dinamik stratejiyle ilgili bilgiler burada bulunur.
- Yerel olarak işlem yürütmenin zorlukları hakkında buradan bilgi edinebilirsiniz.
Yerel kaynak yöneticisi
Bazel birçok işlemi paralel olarak çalıştırabilir. Paralel olarak yürütülmesi gereken yerel işlemlerin sayısı işlemden eyleme farklılık gösterir: Bir işlem ne kadar çok kaynak gerektirirse yerel makineye aşırı yüklenmeyi önlemek için aynı anda daha az örneğin çalıştırılmalıdır.
Bu, ResourceManager
sınıfında uygulanır: Her işleme, bir ResourceSet
örneği (CPU ve RAM) biçiminde gereken yerel kaynakların tahmini bir açıklaması eklenmelidir. Daha sonra işlem bağlamları, yerel kaynaklar gerektiren bir işlem yaptığında ResourceManager.acquireResources()
yöntemini çağırır ve gerekli kaynaklar kullanıma sunulana kadar engellenir.
Yerel kaynak yönetiminin daha ayrıntılı açıklamasını burada bulabilirsiniz.
Çıkış dizininin yapısı
Her işlem, çıkış dizininde, çıkışların yerleştirildiği ayrı bir yere ihtiyaç duyar. Türetilen yapıların konumu genellikle şu şekildedir:
$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>
Belirli bir yapılandırmayla ilişkilendirilen dizinin adı nasıl belirlenir? Çakışan istenen iki özellik vardır:
- Aynı derlemede iki yapılandırma oluşabiliyorsa her ikisinin de aynı işlemin kendi sürümüne sahip olması için farklı dizinlere sahip olmaları gerekir. Aksi takdirde, aynı çıkış dosyasını üreten bir işlemin komut satırı gibi iki yapılandırma arasında anlaşamaması durumunda, Bazel hangi işlemi seçeceğini ("işlem çakışması") bilemez.
- İki yapılandırma "kabaca" aynı şeyi temsil ediyorsa bu yapılandırmaların aynı ada sahip olması gerekir. Böylece, bir yapılandırmada yürütülen işlemler, komut satırları eşleşirse diğerinde yeniden kullanılabilir. Örneğin, Java derleyici için komut satırı seçeneklerinde yapılan değişiklikler, C++ derleme işlemlerinin yeniden çalıştırılmasına yol açmamalıdır.
Şu ana kadar, bu sorunu çözmek için yapılandırma kırpma sorunuyla benzerlikleri olan ilkeli bir yöntem bulamadık. Seçenekler hakkında daha uzun bir açıklamayı burada bulabilirsiniz. Başlıca sorunlu alanlar, Starlark kuralları (yazarlar genellikle Bazel'i tam olarak bilmezler) ve özelliklerdir. Bunlar, "aynı" çıktı dosyasını oluşturabilecek şeylerin alanına yeni bir boyut katar.
Mevcut yaklaşım, yapılandırmanın yol segmentinin, Java'da uygulanan yapılandırma geçişlerinin işlem çakışmalarına yol açmaması için çeşitli sonekler eklenmiş şekilde <CPU>-<compilation mode>
olmasıdır. Buna ek olarak, kullanıcıların işlem çakışmalarına neden olmaması için Starlark yapılandırma geçişleri grubunun bir sağlama toplamı eklenir. Bu yöntem mükemmel değildir. Bu, OutputDirectories.buildMnemonic()
içinde uygulanır ve her yapılandırma parçasının çıkış dizininin adına kendi parçasını eklemesine dayanır.
Testler
Bazel, test çalıştırmak için zengin desteğe sahiptir. Şunları destekler:
- Testleri uzaktan çalıştırma (uzaktan yürütme arka ucu varsa)
- Testleri paralel olarak birden çok kez çalıştırmak (zamanlama verilerini ayıklamak veya toplamak için)
- Testleri parçalama (hız için aynı testteki test durumlarını birden fazla işlem üzerinden bölme)
- Güvenilir olmayan testleri yeniden çalıştırma
- Testleri test paketleri halinde gruplandırma
Testler, TestProvider'a sahip olan ve testin nasıl çalıştırılması gerektiğini açıklayan normal yapılandırılmış hedeflerdir:
- Yapısı sonucunda testin çalıştırıldığı yapılar. Bu, serileştirilmiş
TestResultData
mesajı içeren bir "önbellek durumu" dosyasıdır - Testin çalıştırılma sayısı
- Testin bölünmesi gereken parça sayısı
- Testin nasıl çalıştırılması gerektiğiyle ilgili bazı parametreler (test zaman aşımı gibi)
Hangi testlerin çalıştırılacağını belirleme
Hangi testlerin gerçekleştirileceğini belirlemek ayrıntılı bir süreçtir.
İlk olarak, hedef kalıp ayrıştırması sırasında test paketleri yinelemeli bir şekilde genişletilir. Genişletme işlemi TestsForTargetPatternFunction
üzerinde uygulanır. Bir test paketinin test bildirmediğini belirtmesi, paketindeki her testi kapsaması biraz şaşırtıcı bir kırışıklıktır. Bu işlem, Package.beforeBuild()
ürününde test paketi kurallarını test etmek için $implicit_tests
adlı örtülü bir özellik eklenerek uygulanır.
Ardından testler, komut satırı seçeneklerine göre boyut, etiketler, zaman aşımı ve dil için filtrelenir. Bu, TestFilter
içinde uygulanır ve hedef ayrıştırma sırasında TargetPatternPhaseFunction.determineTests()
konumundan çağrılır ve sonuç TargetPatternPhaseValue.getTestsToRunLabels()
öğesine yerleştirilir. Filtrelenebilecek kural özelliklerinin yapılandırılamamasının nedeni, bu işlemin analiz aşamasından önce gerçekleşmesidir, bu nedenle yapılandırmanın kullanılamamasıdır.
Bu değer daha sonra BuildView.createResult()
içinde daha fazla işlenir: Analizi başarısız olan hedefler filtrelenir ve testler, özel ve münhasır olmayan testlere ayrılır. Ardından, AnalysisResult
içine yerleştirilir. ExecutionTool
bu şekilde hangi testleri çalıştıracağını bilir.
Bu ayrıntılı sürece biraz şeffaflık kazandırmak için, komut satırında belirli bir hedef belirtildiğinde hangi testlerin çalıştırılacağını öğrenmek amacıyla tests()
sorgu operatörü (TestsFunction
uygulamasında uygulanmıştır) kullanılabilir. Ne yazık ki bir yeniden uygulama olduğundan muhtemelen birkaç şekilde yukarıdakilerden sapacaktır.
Testler yapılıyor
Testlerin çalıştırılma şekli, önbellek durumu yapıları istemektir. Daha sonra bir TestRunnerAction
yürütülür ve bu da sonunda testi istenen şekilde çalıştıran --test_strategy
komut satırı seçeneği tarafından seçilen TestActionContext
öğesini çağırır.
Testler, kendilerinden ne beklendiğini testlere bildirmek için ortam değişkenlerini kullanan ayrıntılı bir protokole göre çalıştırılır. Bazel'in testlerden beklentileri ve Bazel'den hangi testlerin bekleyebileceği hakkında ayrıntılı açıklamayı burada bulabilirsiniz. En basit şekilde, çıkış kodunun 0 olması başarı, diğer her şey ise başarısız olduğu anlamına gelir.
Önbellek durum dosyasına ek olarak, her test işlemi başka bir dizi dosya yayınlar. Bunlar, hedef yapılandırmanın çıkış dizininin testlogs
adlı alt dizini olan "test günlüğü dizinine" yerleştirilir.
test.xml
, test kırığındaki her bir test durumunu ayrıntılı şekilde gösteren JUnit stili bir XML dosyasıdırtest.log
, testin konsol çıkışıdır. stdout ve stderr ayrılmaz.test.outputs
, "bildirilmemiş çıkışlar dizini"dir. Terminale yazdırdıklarına ek olarak dosya çıkışını isteyen testler tarafından kullanılır.
Test yürütülürken normal hedefler oluşturulurken gerçekleşebilecek iki şey vardır: özel test yürütme ve çıkış akışı.
Bazı testlerin özel modda (örneğin, diğer testlere paralel olarak değil) yürütülmesi gerekir. Bu, test kuralına tags=["exclusive"]
eklenerek veya testi --test_strategy=exclusive
ile çalıştırarak elde edilebilir . Her özel test, "ana" derlemeden sonra testin yürütülmesini isteyen ayrı bir Skyframe çağrısı tarafından yürütülür. Bu özellik, SkyframeExecutor.runExclusiveTest()
üzerinde uygulandı.
İşlem tamamlandığında terminal çıkışı dökümü alınan normal işlemlerin aksine kullanıcı, uzun süreli testin ilerlemesi hakkında bilgi almak için testlerin sonucunun akışa aktarılmasını isteyebilir. Bu, --test_output=streamed
komut satırı seçeneğiyle belirtilir ve farklı testlerin çıkışlarının karışmaması için özel test yürütülmesi anlamına gelir.
Bu işlem, uygun şekilde adlandırılmış StreamedTestOutput
sınıfında uygulanır ve söz konusu testin test.log
dosyasında yapılan değişiklikler anketlanır ve Bazel kurallarının olduğu terminale yeni baytlar aktarılır.
Yürütülen testlerin sonuçları, çeşitli etkinlikler (TestAttempt
, TestResult
veya TestingCompleteEvent
gibi) gözlemlenerek etkinlik yolunda kullanılabilir. Bu testler, Derleme Etkinliği Protokolü'ne aktarılır ve AggregatingTestListener
tarafından konsola yayınlanır.
Kapsam koleksiyonu
Kapsam, dosyalardaki LCOV biçiminde testler tarafından raporlanır
bazel-testlogs/$PACKAGE/$TARGET/coverage.dat
Kapsamın toplanması için her test yürütme, collect_coverage.sh
adlı bir komut dosyasına sarmalanır .
Bu komut dosyası, test ortamını oluşturarak kapsam koleksiyonunu etkinleştirir ve kapsam çalışma zamanları tarafından kapsam dosyalarının nereye yazıldığını belirler. Daha sonra testi çalıştırır. Bir testin kendisi birden çok alt işlem yürütebilir ve birden fazla farklı programlama dilinde (ayrı kapsam toplama çalışma zamanlarıyla) yazılmış bölümlerden oluşabilir. Sarmalayıcı komut dosyası, gerekirse sonuçta elde edilen dosyaları LCOV biçimine dönüştürmekten sorumludur ve bunları tek bir dosyada birleştirir.
collect_coverage.sh
arasındaki konum, test stratejileri tarafından yapılır ve test girişlerinde collect_coverage.sh
öğesinin olmasını gerektirir. Bu, --coverage_support
yapılandırma işaretinin değerine çözümlenen örtülü :coverage_support
özelliği tarafından sağlanır (bkz. TestConfiguration.TestOptions.coverageSupport
).
Bazı diller çevrimdışı enstrümantasyonu yapar, yani kapsam araçları derleme sırasında (ör. C++) eklenirken bazıları da online enstrümantasyon gerçekleştirir. Yani, kapsam araçları yürütme sırasında eklenir.
Diğer bir temel kavram da referans kapsamdır. Bu, bir kitaplık, ikili program veya testte hiçbir kod çalıştırılıp çalıştırılmadığını kapsar. Bu çözümün çözdüğü sorun, bir ikili program için test kapsamını hesaplamak istediğinizde tüm testlerin kapsamını birleştirmenin yeterli olmamasıdır. Çünkü ikili dosyada, herhangi bir teste bağlı olmayan kod olabilir. Dolayısıyla yaptığımız şey, yalnızca kapsama dahil ettiğimiz dosyaları (kapsanan hiçbir satır olmadan) içeren her ikili program için bir kapsam dosyası yayınlamaktır. Bir hedef için referans kapsam dosyası bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat
şeklindedir . Ayrıca, --nobuild_tests_only
işaretini Bazel'e iletirseniz testlere ek olarak ikili programlar ve kitaplıklar için de oluşturulur.
Temel kapsam şu anda bozuk.
Her kuralda kapsam toplama işlemi için iki dosya grubu izlenir: araç kullanılan dosya grubu ve araç meta veri dosyası grubu.
Enstrümantasyonlu dosyalar grubu da adından da anlaşılacağı üzere bir dizi dosyadan ibarettir. Online kapsam çalışma zamanlarında bu değer, çalışma zamanında hangi dosyaların kullanılacağına karar vermek için kullanılabilir. Ayrıca temel kapsamı uygulamak için de kullanılır.
Araç meta veri dosyaları grubu, bir testin Bazel'in ihtiyaç duyduğu LCOV dosyalarını oluşturması için gereken ekstra dosyalar kümesidir. Pratikte bu, çalışma zamanına özel dosyalardan oluşur; örneğin, derleme sırasında gcc .gcno dosyalarını yayar. Kapsam modu etkinleştirilirse bunlar test işlemleri giriş grubuna eklenir.
Kapsamın toplanıp toplanmadığı BuildConfiguration
içinde saklanır. Test işlemini ve eylem grafiğini bu bite bağlı olarak değiştirmenin kolay bir yolu olduğundan bu yöntem faydalıdır. Ancak bu bit değiştirilirse tüm hedeflerin yeniden analiz edilmesi gerekir (C++ gibi bazı diller, kapsamı toplayabilecek kod yaymak için farklı derleyici seçenekleri gerektirir ve bu durumda da yeniden analiz gerekir. Bu da yine de yeniden analiz yapılması gerekir).
Kapsam destek dosyaları, örtülü bağımlılıktaki etiketlerle temel alınır. Böylece, çağrı politikası tarafından geçersiz kılınabilirler. Bu sayede, Bazel'ın farklı sürümleri arasında farklılıklar bulunabilir. İdeal olarak, bu farklar giderilir ve biz de bunlardan birini standart hale getiririz.
Ayrıca, bir Bazel çağrısında her test için toplanan kapsamı birleştiren bir "kapsam raporu" oluştururuz. Bu, CoverageReportActionFactory
tarafından işlenir ve BuildView.createResult()
kaynağından çağrılır . Yürütülen ilk testin :coverage_report_generator
özelliğine bakarak ihtiyaç duyduğu araçlara erişim sağlar.
Sorgu motoru
Bazel'in çeşitli grafikler hakkında çeşitli sorular sormak için kullandığı küçük bir dil vardır. Aşağıdaki sorgu türleri sağlanır:
- Hedef grafiği incelemek için
bazel query
kullanılır bazel cquery
, yapılandırılmış hedef grafiğini incelemek için kullanılırbazel aquery
, işlem grafiğini incelemek için kullanılır.
Bunların her biri, AbstractBlazeQueryEnvironment
alt sınıfıyla uygulanır.
QueryFunction
alt sınıflandırılmasıyla ek sorgu işlevleri yapılabilir. Akış sorgusu sonuçlarına izin vermek için bunları bir veri yapısına toplamak yerine QueryFunction
öğesine bir query2.engine.Callback
iletilir. Bu da, döndürmek istediği sonuçlar için bu sonuçları çağırır.
Bir sorgunun sonucu çeşitli şekillerde yayınlanabilir: etiketler, etiketler ve kural sınıfları, XML, protobuf vb. Bunlar, OutputFormatter
alt sınıfları olarak uygulanır.
Bazı sorgu çıkış biçimlerinin (protokol kısmı kesinlikle) ince bir şartı, paketi yüklemenin çıktıyı farklılaştırabilmesi ve belirli bir hedefin değişip değişmediğini belirleyebilmesi için Bazel'in paket yüklemenin sağladığı bilgileri yaymasıdır. Sonuç olarak, özellik değerlerinin seri hale getirilebilir olması gerekir. Bu nedenle, hiçbir özelliği karmaşık Starlark değerlerine sahip olmayan çok az özellik türü vardır. Normal çözüm, bir etiket kullanmak ve karmaşık bilgileri bu etikete sahip kurala eklemektir. Pek tatmin edici bir çözüm değil ve bu gereksinimin kaldırılması çok iyi olacaktır.
Modül sistemi
Bazel, üzerine modül eklenerek genişletilebilir. Her modül BlazeModule
alt sınıfı olmalıdır (Bazel eskiden Blaze olarak biliniyordu. Bu geçmişe ait bir kalıntıdır) ve bir komut yürütülürken çeşitli etkinlikler hakkında bilgi verilir.
Çoğunlukla, yalnızca Bazel'in bazı sürümlerinde (Google'da kullandığımız sürüm gibi) ihtiyaç duyulan çeşitli "temel olmayan" işlevsellik parçalarını uygulamak için kullanılırlar:
- Uzaktan yürütme sistemlerine yönelik arayüzler
- Yeni komutlar
BlazeModule
tarafından sunulan uzantı noktaları grubu rastgele. Bunu iyi tasarım ilkelerine
örnek olarak kullanmayın.
Etkinlik otobüsü
BlazeModules'un Bazel'in geri kalanıyla iletişim kurmak için kullandığı ana yöntem etkinlik otobüsü (EventBus
) şeklindedir: Her derleme için yeni bir örnek oluşturulur, Bazel'in çeşitli bölümleri bu örneğe etkinlik gönderebilir ve modüller, ilgilendikleri etkinlikler için dinleyiciler kaydedebilir. Örneğin, aşağıdakiler etkinlik olarak temsil edilir:
- Oluşturulacak derleme hedeflerinin listesi belirlendi
(
TargetParsingCompleteEvent
) - Üst düzey yapılandırmalar belirlendi
(
BuildConfigurationEvent
) - Başarıyla oluşturulmuş bir hedefin oluşturulup oluşturulmadığı (
TargetCompleteEvent
) - Bir test çalıştırıldı (
TestAttempt
,TestSummary
)
Bu etkinliklerden bazıları, Etkinlik Oluşturma Protokolü'nde Bazel dışında temsil edilir (BuildEvent
olur). Bu, yalnızca BlazeModule
'lere değil, aynı zamanda Bazel işlemi dışındaki öğelerin de derlemeyi gözlemlemesini sağlar. Bunlara, protokol mesajlarını içeren bir dosya olarak erişilebilir ya da Bazel etkinlik akışı sağlamak için bir sunucuya (Derleme Etkinliği Hizmeti olarak adlandırılır) bağlanabilir.
Bu işlev, build.lib.buildeventservice
ve build.lib.buildeventstream
Java paketlerinde uygulanır.
Harici depolar
Bazel başlangıçta bir monorepo'da (yapılması gereken her şeyi içeren tek bir kaynak ağacında) kullanılmak üzere tasarlanmıştı. Ancak Bazel bunun her zaman geçerli olmadığı bir dünyada yaşıyor. "Harici depolar", bu iki dünya arasında köprü oluşturmak için kullanılan bir soyutlamadır: Derleme için gerekli olan ancak ana kaynak ağacında bulunmayan kodu temsil ederler.
WORKSPACE dosyası
Harici depo grubu, WORKSPACE dosyasının ayrıştırılmasıyla belirlenir. Örneğin, şöyle bir beyan:
local_repository(name="foo", path="/foo/bar")
@foo
adlı depodaki sonuçlar kullanılabilir. Bu durum karmaşık hale gelir. Starlark dosyalarında yeni depo kuralları tanımlanabilir. Bu kurallar daha sonra yeni Starlark kodunu yüklemek için kullanılabilir. Bu kod, yeni depo kurallarını tanımlamak için kullanılabilir ve bu şekilde devam eder.
Bu durumu ele almak için WORKSPACE dosyasının (WorkspaceFileFunction
içindeki) ayrıştırılması, load()
ifadeleriyle açıklanan parçalara ayrılmıştır. Parça dizini, WorkspaceFileKey.getIndex()
ile gösterilir ve X
endeksinin X'inci load()
ifadesine kadar değerlendirilmesi anlamına gelene kadar
hesaplamak WorkspaceFileFunction
ile gösterilir.
Kod depoları getiriliyor
Kod deposunun kodu Bazel'in kullanımına sunulmadan önce kodun fetched gerekir. Bu, Bazel'in $OUTPUT_BASE/external/<repository name>
altında bir dizin oluşturmasına neden olur.
Kod deposunu getirme işlemi aşağıdaki adımlarla gerçekleşir:
PackageLookupFunction
, bir depoya ihtiyacı olduğunu fark eder veSkyKey
olarakRepositoryLoaderFunction
çağıran birRepositoryName
oluştururRepositoryLoaderFunction
, belirsiz nedenlerle isteğiRepositoryDelegatorFunction
adresine yönlendirir (kodda, Skyframe yeniden başlatılırsa şeylerin yeniden indirilmesinden kaçınılması gerektiği belirtilse de bu pek geçerli bir neden değildir)RepositoryDelegatorFunction
, istenen depo bulunana kadar WORKSPACE dosyasının parçaları üzerinde yineleme yaparak getirmesi istenen depo kuralını bulur- Depo getirme işlemini uygulayan uygun
RepositoryFunction
bulundu. Bu, deponun Starlark uygulaması veya Java'da uygulanan depolar için kodlu bir haritadır.
Bir depo getirmek çok pahalı olabileceğinden, çeşitli önbelleğe alma katmanları vardır:
- İndirilen dosyalar için sağlama toplamı (
RepositoryCache
) ile girilen bir önbellek vardır. Bu, sağlama toplamının WORKSPACE dosyasında mevcut olmasını gerektirir, ancak yine de hermetik açıdan iyidir. Bu özellik, çalıştıkları çalışma alanı veya çıkış tabanından bağımsız olarak aynı iş istasyonundaki her Bazel sunucu örneği tarafından paylaşılır. $OUTPUT_BASE/external
altındaki her depo için bir "işaretçi dosyası" yazılır. Bu dosya, veri deposunu getirmek için kullanılan kuralın sağlama toplamını içerir. Bazel sunucusu yeniden başlatılırsa ancak sağlama toplamı değişmezse, yeniden getirilmez. Bu,RepositoryDelegatorFunction.DigestWriter
üzerinde uygulanır .--distdir
komut satırı seçeneği, indirilecek yapıları aramak için kullanılan başka bir önbelleği belirtir. Bu, Bazel'in internetten rastgele öğeler getirmemesi gereken kurumsal ortamlarda yararlıdır. Bu,DownloadManager
tarafından uygulanır .
Depo indirildikten sonra içindeki yapılar kaynak yapıları olarak değerlendirilir. Bu durum bir sorun teşkil eder. Çünkü Bazel, genellikle kaynak yapılarda stat() çağrısı yaparak güncellik olup olmadığını kontrol eder. Ayrıca, bu yapılar ayrıca depolarının tanımı değiştiğinde geçersiz hale gelir. Bu nedenle, harici depodaki bir yapı için FileStateValue
'lerin harici depolarına bağlı olması gerekir. Bu işlem ExternalFilesHelper
tarafından işlenecek.
Yönetilen dizinler
Bazen harici depoların, çalışma alanı kökü altındaki dosyaları (indirilen paketleri kaynak ağacının bir alt dizininde barındıran paket yöneticisi gibi) değiştirmesi gerekir. Bu, Bazel'in kaynak dosyaların tek başına değil, yalnızca kullanıcı tarafından değiştirildiğini ve paketlerin çalışma alanı kökü altındaki her dizine başvuruda bulunmasına izin verdiğini varsaymasıyla çelişir. Bazel, bu tür bir dış depo çalışması yapabilmek için iki şey yapıyor:
- Kullanıcının, Bazel'in erişmesine izin verilmeyen çalışma alanının alt dizinlerini belirtmesine olanak tanır. Bunlar,
.bazelignore
adlı bir dosyada listelenir ve işlev,BlacklistedPackagePrefixesFunction
ürününde uygulanır. - Çalışma alanının alt dizininden elde edilen eşlemeyi, yönetildiği harici depoya kodlayıp
ManagedDirectoriesKnowledge
olarak kodlayıpFileStateValue
'ları normal harici depolarla aynı şekilde ele alırız.
Depo eşlemeleri
Birden fazla depo, aynı depoya farklı sürümlerde bağımlı olmak isteyebilir (bu, "elmas bağımlılık sorununun bir örneğidir"). Örneğin, derlemede ayrı depolardaki iki ikili program Guava'ya bağımlı olmak isterse her ikisi de muhtemelen @guava//
ile başlayan etiketlerle Guava'yı belirtir ve bunun farklı sürümleri anlamına gelmesini bekler.
Bu nedenle Bazel, harici depo etiketlerinin yeniden eşlenmesine olanak tanır. Böylece @guava//
dizesi, bir ikili programın deposundaki bir Guava deposuna (@guava1//
gibi) ve diğerinin deposuna (@guava2//
gibi) başka bir Guava deposuna referans verebilir.
Alternatif olarak bu, elmasları join için de kullanılabilir. Bir depo @guava1//
, bir diğeri ise @guava2//
bağımlıysa depo eşleme, standart @guava//
deposu kullanmak için her iki deponun da yeniden eşlenmesine olanak tanır.
Eşleme, WORKSPACE dosyasında bağımsız depo tanımlarının repo_mapping
özelliği olarak belirtilir. Ardından, Skyframe'de WorkspaceFileValue
ürününün bir üyesi olarak görünür ve burada:
Package.Builder.repositoryMapping
, paketteki kuralların etiket değerli özellikleriniRuleClass.populateRuleAttributeValues()
- Analiz aşamasında kullanılan
Package.repositoryMapping
(yükleme aşamasında ayrıştırılmamış$(location)
gibi şeyleri çözmek için) - load() ifadelerindeki etiketleri çözümlemek için
BzlLoadFunction
JNI bitleri
Bazel sunucusu genellikle Java dilinde yazılmıştır. Bunun istisnası, Java'nın kendi başına yapamadığı veya uygulama sırasında kendi başına yapamadığı kısımlarıdır. Bunun nedeni çoğunlukla dosya sistemi, işlem denetimi ve diğer alt düzey şeylerle olan etkileşimdir.
C++ kodu src/main/native altında bulunur ve yerel yöntemlere sahip Java sınıfları şunlardır:
NativePosixFiles
veNativePosixFileSystem
ProcessUtils
WindowsFileOperations
veWindowsFileProcesses
com.google.devtools.build.lib.platform
Konsol çıkışı
Konsol çıkışı yayınlamak basit bir şey gibi görünse de birden fazla işlem (bazen uzaktan) çalıştırmanın birleşmesi, ayrıntılı önbelleğe alma, güzel ve renkli bir terminal çıkışına sahip olma isteği ve uzun süre çalışan bir sunucuya sahip olma isteği onu önemsiz hale getirir.
RPC çağrısı istemciden geldikten hemen sonra, yazdırılan verileri istemciye yönlendiren iki RpcOutputStream
örneği oluşturulur (stdout ve stderr için). Daha sonra bunlar bir OutErr
(stdout, stderr) çifti içine sarmalanır. Konsolda yazdırılması gereken her şey bu akışlardan geçer. Daha sonra bu akışlar BlazeCommandDispatcher.execExclusively()
bölgesine aktarılır.
Çıktı, varsayılan olarak ANSI çıkış sıralarıyla yazdırılır. Bunlar istenmediği zaman (--color=no
) bir AnsiStrippingOutputStream
ile çıkarılır. Ek olarak, System.out
ve System.err
bu çıkış akışlarına yönlendirilir.
Böylece hata ayıklama bilgileri System.err.println()
kullanılarak yazdırılabilir ve istemcinin terminal çıkışında (sunucudan farklıdır) bulunabilir. Bir işlem ikili çıktı (bazel query --output=proto
gibi) üretirse stdout'un gerçekleştirilmemesine dikkat edilir.
Kısa mesajlar (hatalar, uyarılar vb.) EventHandler
arayüzü üzerinden ifade edilir. Bunların, EventBus
içeriğindeki yayınlardan çok farklı olduğu dikkat çekicidir (kafa karıştırıcıdır). Her Event
bir EventKind
içerir (hata, uyarı, bilgi ve birkaç tane daha). Ayrıca, Location
(kaynak kodda etkinliğin gerçekleştiği yer) olabilir.
Bazı EventHandler
uygulamaları, aldıkları etkinlikleri depolar. Bu özellik, önbelleğe alınmış çeşitli işlemlerin (ör. önbelleğe alınmış yapılandırılmış bir hedefin yayınladığı uyarılar) neden olduğu çeşitli önbellek işlemlerinin neden olduğu bilgileri kullanıcı arayüzünde tekrar oynatmak için kullanılır.
Bazı EventHandler
'ler, sonunda etkinlik yoluna giden yolu bulan etkinliklerin yayınlanmasına da izin verir (normal Event
'ler burada _not _görünmez). Bunlar ExtendedEventHandler
uygulamalarıdır ve temel kullanımları, önbelleğe alınan EventBus
etkinliklerini tekrar oynatmaktır. Bu EventBus
etkinliklerinin tümü Postable
uygular, ancak EventBus
ürününe yayınlanan her şey bu arayüzü uygulamaz; yalnızca ExtendedEventHandler
tarafından önbelleğe alınanlar (bu güzel olur ve işlemlerin çoğu yapar; yine de zorunlu kılınmaz)
Terminal çıkışı çoğunlukla UiEventHandler
aracılığıyla yayınlanır. Bu, Bazel'in yaptığı tüm süslü çıkış biçimlendirmesi ve ilerleme raporlamasından sorumludur. İki girişi vardır:
- Etkinlik otobüsü
- Rapor eden aracılığıyla eklenen etkinlik akışı
Komut yürütme makinelerinin (örneğin, Bazel'ın geri kalanı) istemciye RPC akışıyla tek doğrudan bağlantısı Reporter.getOutErr()
üzerinden gerçekleşir. Bu bağlantı, bu akışlara doğrudan erişim sağlar. Yalnızca bir komutun büyük miktarlarda olası ikili program verisi (bazel query
gibi) dökümü alması gerektiğinde kullanılır.
Bazel Profil Oluşturma
Bazel hızlı. Bazel de yavaştır çünkü derlemeler mümkün olanın sınırına kadar
büyüme eğilimindedir. Bu nedenle Bazel'de, yapı profilleri ve Bazel'in kendisi için kullanılabilecek bir profil aracı bulunur. Uygun şekilde Profiler
adlı bir sınıfta uygulanmıştır. Varsayılan olarak etkindir. Ancak, ek yükünün tolere edilebilir olması için yalnızca kısaltılmış verileri kaydeder. Komut satırı
--record_full_profiler_data
, mümkün olan her şeyi kaydetmesini sağlar.
Chrome profili biçiminde bir profil oluşturur. En iyi şekilde Chrome'da görüntülenir. Veri modeli görev yığınları şeklindedir: Görevler bir şekilde başlatılıp bitirilebilir ve bunların birbirlerine düzgün bir şekilde yerleştirilmiş olması gerekir. Her Java iş parçacığının kendi görev yığını vardır. YAPILACAKLAR: Bunlar, eylemler ve devamlı atlama stiliyle nasıl çalışır?
Profil oluşturucu, sırasıyla BlazeRuntime.initProfiler()
ve BlazeRuntime.afterCommand()
için başlatılır ve durdurulur ve her şeyin profilini çıkarabilmek için mümkün olduğunca uzun süre yayında kalmaya çalışır. Profile öğe eklemek için
Profiler.instance().profile()
numaralı telefonu arayın. Kapanışı görevin sonunu temsil eden bir Closeable
döndürür. "Kaynaklarla deneyin" ifadelerinin kullanılması en iyisidir.
MemoryProfiler
ürününde de temel bellek profili oluşturma işlemi gerçekleştiriyoruz. Ayrıca her zaman açıktır ve çoğunlukla maksimum yığın boyutlarını ve GC davranışını kaydeder.
Bazel'i test etme
Bazel'in iki tür test vardır: Bazel'i "kara kutu" olarak gözlemleyen testler ve yalnızca analiz aşamasında yürütülen testler. Öncekilerine "entegrasyon testleri" ve sonrakilere "birim testleri" adını veriyoruz. Ancak bu testler, daha az entegre edilmiş entegrasyon testlerine benzer. Ayrıca, gerekli olduğu bazı gerçek birim testlerimiz de vardır.
Entegrasyon testlerinin iki türü vardır:
src/test/shell
altında çok ayrıntılı bir bash testi çerçevesi kullanılarak uygulananlar- Java'da uygulananlar. Bunlar,
BuildIntegrationTestCase
alt sınıfları olarak uygulanır
BuildIntegrationTestCase
, çoğu test senaryosu için gerekli donanıma sahip olduğundan tercih edilen entegrasyon testi çerçevesidir. Bir Java çerçevesi olduğundan, birçok yaygın geliştirme aracıyla hata ayıklama ve sorunsuz entegrasyon sağlar. Bazel deposunda birçok BuildIntegrationTestCase
sınıfı örneği bulunmaktadır.
Analiz testleri, BuildViewTestCase
alt sınıfları olarak uygulanır. BUILD
dosyalarını yazmak için kullanabileceğiniz bir tarama dosya sistemi bulunur. Ardından, çeşitli yardımcı yöntemler yapılandırılmış hedefler isteyebilir, yapılandırmayı değiştirebilir ve analizin sonucu hakkında çeşitli bilgiler sunabilir.