Bazel kod tabanı

. Sorun bildirin Kaynağı göster Gece · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Bu belgede, kod tabanı ve Bazel'ın nasıl yapılandırıldığı açıklanmaktadır. Google , son kullanıcıları değil, Bazel'a katkıda bulunmak isteyen kişiler için tasarlanmıştır.

Giriş

Bazel'in kod tabanı büyük (~350KLOC üretim kodu ve ~260 KLOC testi) kimse o işin geneline hakim değil: Herkes kendi işlerini çok iyidir, ama her bölgede tepelerin üstünde neler olduğunu çok az kişi bilir girin.

Yolculuğun ortasında olan insanların kendilerini bir engelin içinde yalın bir patikanın kaybolduğu, karanlık bir ormana bakan bu belgede, kod tabanına ilişkin genel bir bakış sunacaktır. Böylece, anlamaya başladım.

Bazel'ın kaynak kodunun herkese açık sürümü, GitHub'da şu adreste bulunmaktadır: github.com/bazelbuild/bazel adresine gidin. Bu değil "bilgi kaynağı"; Google'ın dahili kaynak ağacından türetilir ve Google dışında yararlı olmayan ek işlevler içeriyor. İlgili içeriği oluşturmak için kullanılan GitHub'ı bilgi kaynağı hâline getirmek.

Katkılar, normal GitHub pull isteği mekanizması üzerinden kabul edilir. ve bir Google çalışanı tarafından manuel olarak dahili kaynak ağacına içe aktarılır, tekrar GitHub'a aktarılmıştı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 iki tür seçenek vardır: başlatma ve komutuna ekleyin. 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 gelir ve bazıları da sonrasında (-c opt); önceki türe "başlangıç seçeneği" denir ve sunucu işlemini bir bütün olarak etkilerken, ikincisinde "komutun seçenek", yalnızca tek bir komutu etkiler.

Her sunucu örneğinin tek bir ilişkili kaynak ağacı ("çalışma alanı") vardır ve çalışma alanında genellikle tek bir etkin sunucu örneği bulunur. Bu ayar atlatılabilir özel bir çıktı tabanı belirterek (daha fazla bilgi için "Dizin düzeni" bölümüne bakın ) ekleyin.

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, yukarıdaki ELF yürütülebilir dosyası C++ ( "client") kontrolü alır. Bu komut dosyasını kullanarak uygun bir sunucu işlemi şu adımları uygulayın:

  1. Dosyanın daha önce çıkarılıp çıkarılmadığını kontrol eder. İlişkilendirilmediğinde bu mümkün değildir. Bu sunucu uygulamasının geldiği yerdir.
  2. Çalışan bir etkin sunucu örneği olup olmadığını kontrol eder: Çalışıyor, doğru çalışma alanı dizinini kullanır. Google $OUTPUT_BASE/server dizinine bakarak çalışan sunucuyu bulur sunucunun dinlediği bağlantı noktasının yer aldığı bir kilit dosyası bulunur.
  3. Gerekirse eski sunucu işlemini sonlandırır
  4. Gerekirse yeni bir sunucu işlemi başlatır

Uygun bir sunucu işlemi hazır olduğunda, çalıştırılması gereken komut bir gRPC arayüzü üzerinden iletildikten sonra Bazel çıktısı dokunun. Aynı anda yalnızca bir komut çalışabilir. Bu parçalarının C++ ve diğer kısımlarında bulunan özel bir kilitleme mekanizması kullanılarak Java. Paralel olarak birden fazla komutu çalıştırmak için bir altyapı vardır. bazel version komutunu başka bir komutla paralel olarak çalıştıramamak, biraz utanç verici. En büyük engel BlazeModule sn. yaşam döngüsüdür. ve BlazeRuntime içinde bazı eyaletler.

Bir komutun sonunda, Bazel sunucusu çıkış kodunu istemciye iletir. dönmelidir. İlginç bir kırışıklık ise bazel run'nin uygulanmasıdır: bu komutun işi, Bazel'ın yeni derlediği bir şeyi çalıştırmak sunucu işleminden çıkarırız, çünkü bunun bir terminali yoktur. Bunun yerine hangi ikili programla ujexec() birlikte çalışacağını ve hangi bağımsız değişkenlerle

Ctrl-C tuşlarına basıldığında, istemci bunu gRPC'de bir İptal çağrısına çevirir komutu mümkün olan en kısa sürede sonlandırmaya çalışır. üçüncü Ctrl-C tuşlarına basarsanız istemci, bunun yerine sunucuya bir SIGKILL gönderir.

İstemcinin kaynak kodu src/main/cpp altında ve protokol için kullanılan protokol sunucuyla iletişim kurma src/main/protobuf/command_server.proto içindedir .

Sunucunun ana giriş noktası BlazeRuntime.main() ve gRPC çağrılarıdır. GrpcServerImpl.run() tarafından işlenir.

Dizin düzeni

Bazel, derleme sırasında biraz karmaşık bir dizin grubu oluşturur. Tam açıklaması Çıkış dizini düzeninde bulunur.

"Çalışma alanı" Bazel'in çalıştırıldığı kaynak ağaçtır. Genellikle karşılık gelen göz atmayı unutmayın.

Bazel, tüm verilerini "çıkış kullanıcısı kökü" altına yerleştirir. Bu genelde $HOME/.cache/bazel/_bazel_${USER} ancak --output_user_root başlatma seçeneği.

"Yükleme tabanı" Bazel'ın çıkarıldığı yerdir. Bu işlem otomatik olarak yapılır. ve her Bazel sürümü, önemli bir rol oynar. Varsayılan olarak $OUTPUT_USER_ROOT/install konumundadır ve değiştirilebilir --install_base komut satırı seçeneğini kullanarak.

"Çıkış tabanı" öğesi, Bazel örneğinin belirli bir çalışma alanı yazar. Her çıkış tabanında en fazla bir Bazel sunucu örneği bulunur devam edebilir. Normalde saat $OUTPUT_USER_ROOT/<checksum of the path to the workspace>. --output_base başlangıç seçeneği kullanılarak değiştirilebilir. Bu, başka şeylerin yanı sıra, sınırlı bir etki yaratarak bir Bazel örneği herhangi bir zamanda herhangi bir çalışma alanında çalışabilir.

Çıkış dizini şunları içerir:

  • $OUTPUT_BASE/external itibarıyla getirilen harici depolar.
  • Tüm kaynağa ilişkin sembolik bağlantıları içeren bir dizin olan exec kökü kodunu ekleyin. Bulunduğu yer: $OUTPUT_BASE/execroot. Etkinlik sırasında çalışma dizini $EXECROOT/<name of main repository> şeklindedir. Bunu $EXECROOT olarak değiştirmeyi planlıyoruz. Ancak bu uzun vadeli bir plandır çünkü çok uyumsuz bir değişikliktir.
  • Derleme sırasında oluşturulan dosyalar.

Komut çalıştırma işlemi

Bazel sunucusu kontrolü ele alıp bu sunucu için şu aşamalar gerçekleşir:

  1. BlazeCommandDispatcher yeni istek hakkında bilgilendirildi. Karar verir komutun çalışması için bir çalışma alanına ihtiyaç duyup duymadığı (komutun sürüm veya sürüm gibi kaynak koduyla hiçbir ilgisi olmayanlar yardım) ve başka bir komutun çalışıp çalışmadığını kontrol eder.

  2. Doğru komut bulundu. Her komut, arayüzü uygulamalıdır BlazeCommand ve @Command ek açıklamasına sahip olmalıdır (bu, bir komutun ihtiyaç duyduğu tüm meta veriler şöyle olsaydı BlazeCommand alanındaki yöntemler tarafından açıklanmaktadır)

  3. Komut satırı seçenekleri ayrıştırılır. Her komutun farklı bir komut satırı vardır @Command ek açıklamasında açıklanan seçenekleri sunar.

  4. Bir etkinlik yolu oluşturulur. Etkinlik otobüsü, gerçekleşen etkinlikleri yardımcı oluyorum. Bunlardan bazıları Bazel dışına nasıl çalıştığını dünyaya anlatmak için gidilir.

  5. Kontrol, komuta geçer. En ilginç komutlar, bir komut dosyası çalıştırma derleme: derleme, test etme, çalıştırma, kapsam vb.: bu işlev BuildTool tarafından uygulandı.

  6. Komut satırındaki hedef kalıplar grubu ayrıştırılır ve //pkg:all ve //pkg/... çözümlendi. Uygulandığı yer: AnalysisPhaseRunner.evaluateTargetPatterns() ve Skyframe'de şu şekilde yeniden düzenlendi: TargetPatternPhaseValue.

  7. Yükleme/analiz aşaması, eylem grafiğini üretmek için çalıştırılır. derleme için yürütülmesi gereken komutların döngüsel grafiğidir).

  8. Yürütme aşaması başlar. Bu, müşteri hizmetleri için gereken her istenen üst düzey hedefleri derlemek için kullanır.

Komut satırı seçenekleri

Bazel çağrısına ilişkin komut satırı seçenekleri OptionsParsingResult nesnesi, bu nesne de "option"dan bir harita içeriyor sınıflar" değerleriyle eşleştirin. Bir "seçenek sınıfı" şunun bir alt sınıfıdır: OptionsBase ve her biriyle ilgili komut satırı seçeneklerini birlikte gruplandırır diğer. Örneğin:

  1. Bir programlama diliyle (CppOptions veya JavaOptions) ilgili seçenekler. Bunlar, FragmentOptions alt sınıfı olmalıdır ve sonunda sarmalanır bir BuildOptions nesnesine dönüştürülebilir.
  2. Bazel'in işlemleri yürütme şekliyle ilgili seçenekler (ExecutionOptions)

Bu seçenekler analiz aşamasında tüketilmek üzere tasarlanmıştır ve ( Java'da RuleContext.getFragment() veya Starlark'ta ctx.fragments aracılığıyla). Bazılarının (örneğin, C++ yapılıp yapılmayacağına tarama dahil olup olmaması gerektiği) okunuyor aşamasında gerçekleşir, ancak bu durumda her zaman tesisat kurulumu gerekir; BuildConfiguration o zaman kullanılamaz. Daha fazla bilgi için "Configurations" (Yapılandırmalar) bölümüne gidin.

UYARI: OptionsBase örneklerinin sabit olduğunu ve bu şekilde kullanabilirsiniz (örneğin, SkyKeys ürününün bir parçası). Böyle bir durum söz konusu değildir ve Bunlarda değişiklik yapmak, Bazel'ı daha zorlayıcı yöntemlerle kırmanın iyi bir yoludur. hata ayıklaması gerekir. Maalesef bunları değiştirilemez hale getirmek büyük bir iştir. (Bir FragmentOptions üzerinde, yapım aşamasından hemen sonra değişiklik yapılması gerekir.) e-posta adresi equals() veya hashCode() olmadan önce sorun yok.)

Bazel, seçenek sınıflarını aşağıdaki yöntemlerle öğrenir:

  1. Bazıları Bazel'e kabloyla bağlı (CommonCommandOptions)
  2. Her Bazel komutundaki @Command ek açıklamasından
  3. ConfiguredRuleClassProvider kaynağından (bunlar, ilgili komut satırı seçenekleridir) ayrı programlama dillerine)
  4. Starlark kuralları kendi seçeneklerini de tanımlayabilir ( burada bulabilirsiniz)

Her seçenek (Starlark tarafından tanımlanan seçenekler hariç), Aşağıdaki gibi bir @Option ek açıklamasına sahip FragmentOptions alt sınıfı komut satırı seçeneğinin adı ve türü ile birlikte yardım metnini belirtin.

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 için seçenekler; Bu örnekte, komut satırı dizesi, com.google.devtools.common.options.Converter

Bazel'in gördüğü şekliyle kaynak ağaç

Bazel, yazılım geliştirme işiyle çalışıyor. Bu işleri okuyarak ve kaynak kodu yorumlama. Bazel'in çalıştığı kaynak kodunun toplamı "çalışma alanı" denir. kod depoları, paketler ve kurallar.

Kod depoları

Bir "depo" geliştiricinin üzerinde çalıştığı bir kaynak ağacıdır; genelde tek bir projeyi temsil eder. Bazel'in atası Blaze, bir monorepo yani derlemeyi çalıştırmak için kullanılan tüm kaynak kodunu içeren tek bir kaynak ağacıdır. Buna karşın Bazel, kaynak kodu birden çok sayfaya yayılan projeleri destekler. ekleyebilirsiniz. Bazel'ın çağrıldığı depoya "ana diğerlerine "harici depolar" adı verilir.

Bir depo, WORKSPACE (veya WORKSPACE.bazel) adlı bir dosyayla dizini de oluşturur. Bu dosya "global" bilgiler içeriyor bütüne harici veri depoları kümesi olabilir. Nasıl çalışır? normal Starlark dosyası, yani diğer Starlark dosyası load(). Bu, genellikle bir deponun ihtiyaç duyduğu depoları çekmek için kullanılır açıkça referans verilen (buna "deps.bzl kalıbı" denir)

Harici depoların kodları bağlantılı veya indirilmiş $OUTPUT_BASE/external

Derlemeyi çalıştırırken tüm kaynak ağacın birleştirilmesi gerekir. bu ana depodaki her paketin sembolü olan SymlinkForest tarafından yapılır $EXECROOT özelliğine ve her harici depoya $EXECROOT/external veya $EXECROOT/.. (ilki tabii ki paket satın almak mümkün değil. ana depoda external olarak adlandırıldı. Bu nedenle CANNOT TRANSLATE

Paketler

Her depo; paketler, ilgili dosyalar ve dosyalar koleksiyonundan oluşur. ve bağımlılıkların belirtilmesidir. Bunlar, BUILD veya BUILD.bazel. Her ikisi de mevcutsa Bazel BUILD.bazel tercih eder; neden neden BUILD dosyanın hâlâ kabul edildiği için Bazel'ın atası Blaze, bu dosyayı dosya adı. Ancak, özellikle dönüşüm hunisinin üst kısmındaki Windows'da çalışır. Bu sistemde dosya adları büyük/küçük harfe duyarlı değildir.

Paketler birbirinden bağımsızdır: Paketin BUILD dosyasında yapılan değişiklikler diğer paketlerin değişmesine neden olamaz. BUILD dosyanın eklenmesi veya kaldırılması Yinelenen glob'lar paket sınırlarında durduğu için _can _change diğer paketleri Böylece bir BUILD dosyasının varlığı, yinelemeyi durdurur.

BUILD dosyasının değerlendirilmesine "paket yükleme" adı verilir. Uygulandı PackageFactory sınıfında çalışır. Starlark çevirmeni mevcut kural sınıfları kümesi hakkında bilgi gerektirir. Paketin sonucu yükleme, Package nesnesidir. Çoğunlukla bir dizeden (bir bir hedef) ekleyebilirsiniz.

Paket yükleme sırasındaki karmaşıklığın büyük bir kısmı globbing (karmaşıklık) sorunudur: Bazel her kaynak dosyanın açıkça listelenmesini gerektirir ve bunun yerine glob'ları çalıştırabilir (ör. glob(["**/*.java"])). Kabuktan farklı olarak, aynı işleve sahip yinelemeli alt dizinlere ayrılmalıdır (ancak alt paketlere değil). Bunun için şunlara erişim gerekir: hale getirmeyi amaçlıyoruz. Bu yavaş olabileceği için, paralel ve verimli çalışmasını sağlamak sizin görevinizdir.

Globbing aşağıdaki sınıflarda uygulanır:

  • LegacyGlobber, hızlı ve keyifli bir SkyFrame'in farkında olmayan küresel küre
  • SkyframeHybridGlobber, Skyframe kullanan ve "Skyframe yeniden başlatılması"ndan kaçınmak için eski globber'ı (aşağıda açıklanmıştır)

Package sınıfının kendisi, yalnızca şu üyeliklere alışmış bazı üyeleri içerir: WORKSPACE dosyasını ayrıştırır ve gerçek paketler için bir anlam ifade etmez. Bu normal paketleri açıklayan nesnelerin alanları görebilirsiniz. Bunlardan bazıları:

  • Depo eşlemeleri
  • Kayıtlı araç zincirleri
  • Kayıtlı yürütme platformları

İdeal olarak, WORKSPACE dosyasını Package, ihtiyaçları karşılamak zorunda kalmayacak şekilde normal paketleri ayrıştırmak her ikisini de seçebilirsiniz. Bu ikisi birbiriyle iç içe olduğu için maalesef bunu yapmak zor ele alacağız.

Etiketler, Hedefler ve Kurallar

Paketler, aşağıdaki türlerde hedeflerden oluşur:

  1. Dosyalar: Derlemenin girdisi veya çıktısı olan öğeler. İçinde Bazel dilindeki bunlara eserler (başka yerde tartışılan) diyoruz. Bazı taraflar, Derleme sırasında oluşturulan dosyalar hedeftir; genelde Bazel'in ilişkili bir etiketi olmamalıdır.
  2. Kurallar: Bunlar, girişlerden çıkışları elde etmeye yönelik adımları açıklar. Onlar genellikle bir programlama diliyle (ör. cc_library, java_library veya py_library), ancak dilden bağımsız bazı uzantılar da var (ör. genrule veya filegroup)
  3. 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; burada repo, Etiketin bulunduğu deponun adıdır içinde pac/kage, BUILD dosyasının bulunduğu dizin, name ise dosya yoludur. dosyasının (etiket bir kaynak dosyaya başvuruda bulunuyorsa), dosyanın paketinden yararlanın. Komut satırında bir hedefe atıfta bulunurken, etiketin bazı bölümleri şunlar atlanabilir:

  1. Depo atlanırsa etiket ana sayfada depodur.
  2. Paket bölümü atlanırsa (name veya :name gibi) etiket alınır dizin paketine (göreli yollar) eklemeniz gerekir. üst düzey referanslar (..) içeren uygulamalara 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 (yani "yerel kurallar", RuleClass yazın). Uzun vadede, dile özgü tüm kuralı Starlark'ta uygulanacak, ancak bazı eski kural ailelerinde (ör. Java veya C++) şu anda hâlâ Java'dadır.

Starlark kural sınıflarının BUILD dosyanın başında içe aktarılması gerekiyor load() ifadesini kullanarak, Java kural sınıfları ise "doğuştan gelen" bilinen ConfiguredRuleClassProvider kuruluşunun kayıtlı olması nedeniyle Bazel.

Kural sınıfları aşağıdaki gibi bilgileri içerir:

  1. Özellikleri (ör. srcs, deps): türleri, varsayılan değerleri, kısıtlamalar vb.
  2. Varsa her bir özelliğe ekli yapılandırma geçişleri ve unsurlar
  3. Kuralın uygulanması
  4. Geçişli bilgi sağlayıcılar için "genellikle" kuralı oluşturur

Terminoloji notu: Kod tabanında genellikle "Kural" anlamına gelir. bir kural sınıfı tarafından oluşturulur. Ancak Starlark'ta ve kullanıcılara yönelik belgelerde "Kural" Yalnızca kural sınıfına atıfta bulunmak için kullanılmalıdır; hedef bir "hedef"tir. Ayrıca RuleClass "class" sınıfına sahip olmasına rağmen kendi kural sınıfı ile hedefler arasında Java devralma ilişkisi yoktur tercih edebilirsiniz.

Gökyüzü Çerçevesi

Bazel'in temelini oluşturan değerlendirme çerçevesi Skyframe'dir. Modeli, sisteme yapı sırasında oluşturulması gereken her şey, herhangi bir veri parçasından bağımlılıklarına işaret eden kenarları olan döngüsel grafik yani projeyi oluşturmak için bilinmesi gereken diğer veri parçalarını içerir.

Grafikteki düğümlere SkyValue denir ve düğümlerin adları SkyKey sn. İkisi de tamamen değiştirilemez. Sadece değiştirilemez nesneler erişilebilir hale getirebilirsiniz. Bu değişkenlik hemen hemen her zaman geçerlidir ve (örneğin, şunun üyesi olan BuildOptions bağımsız seçenek sınıfları için: BuildConfigurationValue ve SkyKey gibi) değişiklik yapmamak için yalnızca dışarıdan gözlemlenemeyecek şekillerde değiştirmelidir. Bu şekilde, Skyframe içinde hesaplanan her şeyin (örneğin, yapılandırılmış hedefler) sabit de olmalıdı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. En iyisi çok büyük olabildiği için bunu küçük derlemeler için de yapabilirsiniz.

Skyframe, com.google.devtools.build.skyframe paketinde bulunur. İlgili içeriği oluşturmak için kullanılan benzer ada sahip com.google.devtools.build.lib.skyframe paketi Skyframe'in üzerine Bazel uygulanışı. Skyframe hakkında daha fazla bilgi: burada bulabilirsiniz.

Skyframe, belirli bir SkyKey öğesini SkyValue olarak değerlendirmek için Skyframe SkyFunction (anahtarın türüne karşılık gelir). İşlevin Skyframe'i çağırarak başka bağımlılıklar da isteyebilir çeşitli SkyFunction.Environment.getValue() aşırı yüklemeleri. Bu, bu bağımlılıkları Skyframe’in dahili grafiğine kaydetmenin yan etkisine ve herhangi bir bağımlılığı ortaya çıktığında Skyframe’in fonksiyonu yeniden değerlendireceği unutmayın. Başka bir deyişle, Skyframe'in önbelleğe alma ve artımlı hesaplaması SkyFunction ve SkyValue öğelerinin ayrıntı düzeyi.

SkyFunction, kullanılamayan bir bağımlılık istediğinde, getValue() null değerini döndürür. Bunun ardından fonksiyon, kendisi null döndürüyor. Daha sonra Skyframe, Search Ads 360'taki sonra, işlevi yeniden başlatın (yalnızca bu getValue() çağrısının, null olmayan bir sonuçla başarılı olacağı anlamına gelir.

Bunun sonucunda da SkyFunction işlemi tekrarlamanız gerekir. Ancak bu risk, proje yöneticiliği önbelleğe alınan SkyValues bağımlılığını değerlendirin. Bu nedenle, genellikle şunları yaparak çözer:

  1. Bağımlılıkları gruplar halinde bildirmek (getValuesAndExceptions() kullanarak) yeniden başlatma sayısını sınırlandırın.
  2. SkyValue, farklı tarafından hesaplanan ayrı parçalara ayrılır SkyFunction'leri destekler. Böylece bağımsız olarak hesaplanıp önbelleğe alınabilirler. Bu Hafızayı artırma potansiyeline sahip olduğundan stratejik olarak yapılmalıdır. bazı yolları da görmüştük.
  3. Yeniden başlatmalar arasında durumu depolama SkyFunction.Environment.getState() veya geçici statik önbellek tutma "Skyframe'in arkasında".

Temelde bu tür çözümlere ihtiyacımız var çünkü on binlerce Skyframe düğümü var, ancak Java tercih edebilirsiniz.

Starlark

Starlark, kullanıcıların yapılandırma ve genişletme işlemleri için kullandıkları, alana özgü dildir. Bazel. Python'un çok daha az türü olan kısıtlanmış bir alt kümesi olarak düşünülür; kontrol akışı üzerinde daha fazla kısıtlama ve en önemlisi, güçlü eşzamanlı okuma olanağı sağlar. Bu Turing-complete değil, Bazı kullanıcıları (ancak hepsini değil) genel anlamda bir şeyler yapmaya çalışmaktan caydıran programlama görevlerini öğrenmiş olacaksınız.

Starlark, net.starlark.java paketinde uygulandı. Ayrıca bağımsız bir Go uygulaması vardır. burada bulabilirsiniz. Java Bazel'de kullanılan uygulama şu anda çevirmendir.

Starlark çeşitli bağlamlarda kullanılır. Örneğin:

  1. BUILD dili. Burada yeni kurallar tanımlanır. Starlark kodu bu bağlamda çalıştırıldığında yalnızca BUILD dosyasının içeriğine erişebilir kendisi ve .bzl dosya tarafından yüklendi.
  2. Kural tanımları. Yeni kurallar (örneğin, yeni bir önceden tanımlanmıştır. Bu bağlamda çalışan Starlark kodunun şunlara erişimi vardır: bağımlılıkları tarafından sağlanan yapılandırma ve veriler (bu konuda daha fazla bilgi daha sonra).
  3. WORKSPACE dosyası. Burası, harici depolar ( özellikleri) tanımlanır.
  4. Depo kuralı tanımları. Yeni harici depo türleri bu aşamada oluşturulur tanımlanmıştır. Bu bağlamda çalışan Starlark kodu, çalışma alanı dışına çıkarabilirsiniz.

BUILD ve .bzl dosyalarının lehçeleri biraz farklı ifade eder. Farklılıkların bir listesi mevcut burada bulabilirsiniz.

Starlark hakkında daha fazla bilgi mevcut burada bulabilirsiniz.

Yükleme/analiz aşaması

Yükleme/analiz aşamasında Bazel, bir sonraki aşamaya geçmek için bir kural oluşturacağız. Temel birimi "yapılandırılmış hedef"tir. bir (hedef, yapılandırma) çifti seçin.

Buna "yükleme/analiz aşaması" denir çünkü genelde ikiye önceden seri hâlinde olan ayrı bölümler, artık zaman içinde çakışabiliyor:

  1. Paketler yükleniyor, yani BUILD dosyaları Package nesnelere dönüştürülüyor temsil eden
  2. Yapılandırılmış hedefleri analiz etmek, yani işlem grafiğini oluşturmak için

Yapılandırılmış hedeflerin geçişli kapatma işlemindeki yapılandırılmış her hedef komut satırından istenenler aşağıdan yukarıya analiz edilmelidir; yaprak düğümleri ardından komut satırındakilere taşıyın. Bu analizin girdileri tek bir yapılandırılmış hedef vardır:

  1. Yapılandırma. ("nasıl" kuralının oluşturulacağı; örneğin, kullanıcının gösterilmesini istediği komut satırı seçenekleri gibi ayarlar da C++ derleyicisine geçirilen)
  2. Doğrudan bağımlılıklar. Geçişli bilgi sağlayıcıları kullanılabilir bağlantı kurulabilir. Böyle isimlendiriliyorlar, çünkü çok sayıda "toplayıcı" kapatma işlemiyle ilgili bilgilerin, tüm .jar dosyaları veya listelenen tüm .o dosyaları gibi bir C++ ikili programına bağlı olması gerekir)
  3. Hedefin kendisi. Bu, hedef paketin yüklenmesinin sonucudur içinde. Bunlar, kuralların sahip olduğu özellikleri içerir. önemlidir.
  4. Yapılandırılmış hedefin uygulanması. Kurallarda bu, Starlark'ta veya Java'da olmak. Kuralla yapılandırılmamış tüm hedefler uygulandı yardımcı oldu.

Yapılandırılmış bir hedef analizinin sonucu:

  1. Buna dayalı hedefleri yapılandıran geçişli bilgi sağlayıcılar, erişim
  2. Oluşturabileceği eserler ve bunları üreten eylemler.

Java kurallarına sunulan API, RuleContext API'sinin eşdeğeridir. Starlark kurallarının ctx bağımsız değişkeni. API'si daha güçlü ancak aynı zamanda Örneğin, Bad ThingsTM yapmak daha kolaydır. Örneğin, zamanı veya zamanı alan karmaşıklığı ikinci dereceden (veya daha kötüsü), Bazel sunucusunun Java istisnası veya değişken değerleri ihlal etme (örneğin, bir Options örneğini kullanarak veya yapılandırılmış bir hedefi değişebilir hale getirerek)

Yapılandırılmış bir hedefin doğrudan bağımlılıklarını belirleyen algoritma yaşadığı yer: DependencyResolver.dependentNodeMap().

Yapılandırmalar

Yapılandırmaları "nasıl" hedefi oluşturma: hangi platform için, komut satırı seçenekleri vb.

Bir derlemedeki birden fazla yapılandırma için aynı hedef oluşturulabilir. Bu Örneğin, aynı kod, geliştirme sırasında çalıştırılan bir araç için derlemeye başlarsak ve derlemeye başlarsak veya kocaman bir Android uygulaması (birden fazla CPU için yerel kod içeren bir uygulama) mimariler)

Kavramsal olarak yapılandırma bir BuildOptions örneğidir. Ancak, alıştırma, BuildOptions şunu sağlayan BuildConfiguration ile sarmalanır: ek işlevler sunar. Bu, her bir sayfanın en üstünden en alta inmek lazım. Değişmesi durumunda, derlemenin yeniden analiz edildi.

Bu durum, derleme ya da Örneğin, istenen test çalıştırmalarının sayısı değişir (yalnızca test amaçlı etkileyen faktörlerden biri, test hedeflerini etkiler (bunun hazır değil, ancak hazır değil).

Bir kural uygulamasının yapılandırmanın bir parçası olması gerektiğinde tanımında RuleClass.Builder.requiresConfigurationFragments() kullanarak , Bu, hem hatalardan kaçınmak hem de (Java parçasını kullanan Python kuralları gibi) hem de Böylece Python seçenekleri değişirse C++ hedefin yeniden analiz edilmesi gerekmez.

Bir kuralın yapılandırması, bu kuralın "üst" yapılandırmasıyla aynı değildir kuralı. Bağımlılık ucunda yapılandırmayı değiştirme işlemine "yapılandırma geçişi". Bu durum iki yerde gerçekleşebilir:

  1. Bağımlılık ucunda. Bu geçişler, Attribute.Builder.cfg() ve bir Rule (burada geçişi gerçekleşir) ve BuildOptions (orijinal yapılandırma) veya daha fazla BuildOptions (çıkış yapılandırması).
  2. Yapılandırılmış bir hedefe gelen herhangi bir uçta. Bunlar, RuleClass.Builder.cfg()

İlgili sınıflar: TransitionFactory ve ConfigurationTransition.

Yapılandırma geçişleri kullanılır. Örneğin:

  1. Derleme sırasında belirli bir bağımlılığın kullanıldığını ve bu nedenle yürütme mimarisinde
  2. Birden fazla öğe için belirli bir bağımlılığın oluşturulması gerektiğini mimariler (şişman Android APK'larındaki yerel kod gibi)

Bir yapılandırma geçişi birden fazla yapılandırmayla sonuçlanırsa buna bölünmüş geçiş.

Yapılandırma geçişleri Starlark'ta da uygulanabilir (dokümanlar burada bulabilirsiniz)

Geçiş bilgisi sağlayıcıları

Geçiş bilgisi sağlayıcıları, yapılandırılmış hedeflere yönelik bir yöntemdir (ve _only _way) ve buna bağımlı diğer yapılandırılmış hedefler hakkında bilgi verir. Bunun nedeni "geçişli" bunun bir tür kapsamlı görünümüdür. yapılandırılmış bir hedefin geçişli olarak kapatılması.

Java geçişli bilgi sağlayıcıları arasında genellikle bire bir iletişim vardır. ile Starlark'ı karşılaştırabilirsiniz (istisna, DefaultInfo FileProvider, FilesToRunProvider ve RunfilesProvider, çünkü söz konusu API şuydu: doğrudan Java harf çevirisinden daha Starlark dilindeki gibi kabul edilir). Bunların temeli aşağıdakilerden biridir:

  1. Java Sınıfı nesnesi. Bu, yalnızca Starlark'tan erişilebilir. Bu sağlayıcılar TransitiveInfoProvider
  2. Dizedir. Bu, eski bir yapıya sahiptir ve genellikle çakışmaları yaşanır. Bu tür geçişli bilgi sağlayıcılar, build.lib.packages.Info
  3. Sağlayıcı sembolü. Bu öğe, provider() kullanılarak Starlark'tan oluşturulabilir işlevi görebilir ve yeni sağlayıcı oluşturmak için önerilen yöntemdir. Sembol Java'da bir Provider.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 zamanımız olmadı) ve Starlark'tan TransitiveInfoProvider alt sınıfına erişilemiyor.

Yapılandırılmış hedefler

Yapılandırılmış hedefler RuleConfiguredTargetFactory olarak uygulanır. Bir alt sınıfını kullanır. Starlark yapılandırılmış hedefleri StarlarkRuleConfiguredTargetUtil.buildRule() aracılığıyla oluşturulur .

Yapılandırılmış hedef fabrikalar şu işlemleri yapmak için RuleConfiguredTargetBuilder kullanmalıdır: getiri değerini oluşturmalıdır. Şunlardan oluşur:

  1. Onların filesToBuild, belirsiz bir kavram olan "bu kural ifade eder." Bunlar, yapılandırılan hedef komut satırında veya bir genrule yönergesinin src'sinde yer almalıdır.
  2. Çalıştırma dosyaları, normal ve veriler.
  3. Çıkış grupları. Bunlar çeşitli "diğer dosya gruplarıdır" bu kural, seçeceğiz. Çıkış_grubu özelliği kullanılarak dosya grubu kuralını BUILD'da yayınlamak ve Java'da OutputGroupInfo sağlayıcısını kullanmak anlamına gelir.

Çalıştırma dosyaları

Bazı ikili programların çalışması için veri dosyaları gerekir. Bunun belirgin bir örneği, giriş dosyaları olabilir. Bu, Bazel'de "runfiles" kavramıyla temsil edilir. CEVAP "runfiles ağacı" belirli bir ikili program için veri dosyalarının dizin ağacıdır. Dosya sisteminde tek tek sembolik bağlantılara sahip bir sembolik bağlantı ağacı olarak oluşturulur çıkış ağaçlarının kaynağındaki dosyalara işaret eder.

Çalıştırmalar grubu, Runfiles örneği olarak temsil edilir. Kavramsal olarak çalışma dosyaları ağacındaki bir dosyanın yolundan,Artifact temsil eder. İki kişilik tek bir Map uygulamasından biraz daha karmaşıktır nedenler:

  • Ç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 çeşitli eski giriş türleri bulunur. temsil edilir.

Çalıştırma dosyaları RunfilesProvider kullanılarak toplanır: bu sınıfın bir örneği çalıştırma dosyalarını yapılandırılmış bir hedefi (ör. kitaplık) ve geçişli olarak temsil eder ve bu ihtiyaçlar iç içe yerleştirilmiş bir küme gibi toplanır (aslında bu, (kapak altındaki iç içe yerleştirilmiş kümeler kullanılarak uygulanır): her hedef, çalıştırma dosyalarını birleştirir kendi bağımlılıklarını ekler, kendi bağımlılıklarını ekler ve ardından ortaya çıkan kurulumu olduğunu görüyoruz. RunfilesProvider örneği iki Runfiles içeriyor Bu örnek, kuralın "veriler" aracılığıyla bağlı olduğu ve iki tür bir bağımlılık var. Çünkü bir hedef Bir veri özelliği üzerinden bağlı olduğunda bazen farklı çalıştırma dosyaları sunar çok daha iyidir. Bu, artık gözden kaçırdığımız, istenmeyen eski davranış henüz kaldırılmıyor.

İkili programların çalıştırma dosyaları, RunfilesSupport örneği olarak gösterilir. Bu RunfilesSupport şu özelliklere sahip olduğundan Runfiles ile farklıdır: aslında inşa edilmekte olan (yalnızca bir eşleme olan Runfiles'ın aksine). Bu aşağıdaki ek bileşenleri gerektirir:

  • Runfiles manifesti. Bu, Runfiles ağacı. Runfiles ağacının içeriği için proxy olarak kullanılır ve Bazel, Runfiles ağacının yalnızca içerik barındıran bildiriyor.
  • Çıkış runfiles manifesti. Bu özellik, şu özelliklere sahip çalışma zamanı kitaplıkları tarafından kullanılır: özellikle de Windows'da çalışma dosyalarınızın yanı sıra sembolik bağlantılardan bahsedelim.
  • Runfiles arabulucu. Çalıştırma ağacının var olması için sembolik bağlantı ağacını ve sembollerin işaret ettiği yapıyı oluşturmak için kullanılır. Siparişte etmek isterseniz, runfiles aracısını temsil eder.
  • Komut satırı bağımsız değişkenleri RunfilesSupport nesnesi temsil eder.

Yönler

Oranlar, "hesaplamayı bağımlılık grafiğinde yaymanın" bir yoludur. Bunlar: Bazel kullanıcıları için açıklandı burada bulabilirsiniz. İyi motive edici bir örnek olarak protokol arabellekleri verilebilir: proto_library kuralı, bu tamponların her dil için değil, bir protokolün uygulanmasını herhangi bir programlamada arabellek mesajı ("protokol arabelleklerinin "temel birimi") dil proto_library kuralıyla birleştirilmelidir. Böylece, iki hedefte aynı dil aynı protokol arabelleğine bağlı olduğundan yalnızca bir kez oluşturulur.

Yapılandırılmış hedefler gibi Skyframe'de SkyValue olarak gösterilir ve oluşturulma biçimleri, yapılandırılan hedeflerin yapısına çok benzer. ConfiguredAspectFactory adında bir fabrika sınıfı bulunuyor. bir RuleContext erişimi sağlar, ancak yapılandırılmış hedef fabrikaların aksine Eklendiği yapılandırılmış hedef ve sağlayıcıları hakkında.

Bağımlılık grafiğinde aşağı yayılan yönler, her metrik için özelliğini Attribute.Builder.aspects() işlevini kullanarak öğrenin. Birkaç tane sürece katılan ve kafa karıştırıcı şekilde adlandırılmış sınıflar:

  1. AspectClass, özelliğin uygulanmasıdır. Java'da olabilir. (bu bir alt sınıftır) veya Starlark'ta (bu durumda StarlarkAspectClass örneği). Benzer bir şekilde, RuleConfiguredTargetFactory.
  2. AspectDefinition, özelliğin tanımıdır; şunları içerir: sağlayıcılarını, sağladığı sağlayıcıları ve referanslarını listeleyen (ör. uygun AspectClass örneği) uygulama. İnsanların RuleClass ile benzerdir.
  3. AspectParameters, aşağı yayılan bir özelliği parametriye etmenin bir yoludur bağımlılık grafiğini öğrenebilirsiniz. Şu anda dize eşleme olarak kullanılıyor. İyi bir örnek faydasına dair bir örnek de protokol arabellekleridir: Bir dilde birden fazla API varsa, protokol arabelleklerinin hangi API için oluşturulması gerektiği alta yayılabilir.
  4. Aspect, benzersiz bir özelliği hesaplamak için gereken tüm verileri bağımlılık grafiğinde aşağı doğru yayılır. En boy oranı sınıfını, tanımını ve parametrelerini içerir.
  5. RuleAspect, belirli bir kuralın hangi yönlerini belirleyen işlevdir olmalıdır. Bu bir Rule -> Aspect işlevi.

Bazı yönlerin başka yönlerle bağlantılı olması, Örneğin, bir Java IDE için sınıf yolunu toplayan bir özellik büyük olasılıkla sınıf yolundaki tüm .jar dosyaları hakkında bilgi edinmek istiyorsanız, ancak bunların bazıları protokol arabellekleridir. Bu durumda, IDE (Entegre Geliştirme Ortamı) özelliği (proto_library kuralı + Java proto en boy çifti) çifti.

Belirli yönlerin karmaşıklığını sınıfta ele alınır AspectCollection

Platformlar ve araç zincirleri

Bazel, çoklu platform derlemelerini, yani mümkün olan yerlerde derleme işlemlerinin çalıştırıldığı birden fazla mimari ve yardımcı oluyorum. Bu mimariler Bazel'de platformlar olarak adlandırılır konuşma dili (belgelerin tamamı) burada bulabilirsiniz)

Platform, kısıtlama ayarlarındaki (ör. "CPU mimarisi") kavramını kısıtlı değerlere (örneğin, belirli bir CPU x86_64 gibi). Bir "sözlüğümüz" var kısıtlanmasıyla ilgili @platforms deposundaki ayarları ve değerleri içerir.

Araç zinciri kavramı, hangi platformlara bağlı olarak ve hangi platformlarda hedeflendiği gibi farklı derleyiciler; Örneğin, belirli bir C++ araç zinciri bir ve başka işletim sistemlerini hedefleyebilmeleri gerekir. Bazel, C++ belirlenen yürütme ve hedef platforma göre kullanılan derleyici (araç zincirleri için dokümanlar burada bulabilirsiniz).

Bunu yapmak için araç zincirlerine yürütme kümesi ve destekleyici hedef platform kısıtlamalarını belirlemenize yardımcı olur. Bunu başarabilmek için de projenin tanımı araç zinciri iki bölüme ayrılır:

  1. Yürütme ve hedef grubunu açıklayan toolchain() kuralı bir araç zincirinin desteklediği kısıtlamaları (ör. C++ veya Java) araç zinciri (toolchain_type() kuralıyla temsil edilir)
  2. Gerçek araç zincirini açıklayan dile özgü bir kural (ör. cc_toolchain()) olarak değiştirildi

Bu da böyle yapılır çünkü her bir müşterinin kısıtlarını bilmemiz gerekir. yapmak için araç zinciri aracılığıyla çalışır. *_toolchain() kural bundan çok daha fazla bilgi içerdiğinden daha fazla zaman alır. kalan süreyi ifade eder.

Yürütme platformları aşağıdaki yollardan biriyle belirtilir:

  1. register_execution_platforms() işlevi kullanılarak WORKSPACE dosyasında
  2. Komut satırında --extra_execution_platforms komut satırını kullanarak seçenek

Mevcut yürütme platformları grubu hesaplanırken RegisteredExecutionPlatformsFunction

Yapılandırılmış bir hedef için hedef platform şuna göre belirlenir: PlatformOptions.computeTargetPlatform() Bu bir platform listesi. Çünkü nihayetinde birden çok hedef platformu desteklemek isteyebilirsiniz, ancak bu destek, (henüz).

Yapılandırılmış bir hedef için kullanılacak araç zinciri kümesi ToolchainResolutionFunction İşlevi:

  • Kayıtlı araç zinciri grubu (WORKSPACE dosyasında ve yapılandırma)
  • İstenen yürütme ve hedef platformlar (yapılandırmada)
  • Yapılandırılmış hedefin gerektirdiği araç zinciri türleri kümesi ( UnloadedToolchainContextKey).
  • Yapılandırılmış hedefin ( exec_compatible_with özelliği) ve yapılandırma (--experimental_add_exec_constraints_to_targets), inç UnloadedToolchainContextKey

Bu sonucun sonucu olan UnloadedToolchainContext. Bu aslında öğesinin etiketine araç zinciri türü (ToolchainTypeInfo örneği olarak gösterilir) devreye sokmanız gerekir. Buna "unload" denir çünkü bu bölümü kendi etiketleriyle ilişkilendirmesine yardımcı olur.

Daha sonra araç zincirleri 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" olmasını gerektiren eski bir sistemimiz de var. temsil edilen çeşitli yapılandırma ve hedef yapılandırmalarla --cpu gibi yapılandırma işaretleri için kullanılır . Kademeli olarak yukarıdaki bahsedeceğim. Kullanıcıların eski yapılandırmayı kullandığı durumları ele almak için Google Alışveriş’te platform eşlemeleri kullanıma sunduk. Kodu PlatformMappingFunction dilinde ve Starlark olmayan "küçük" dil" olarak adlandırılır.

Sınırlamalar

Bazen bir kullanıcı bir hedefi yalnızca birkaç tanesiyle uyumlu platformlar. 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, Java kuralları için çoğunlukla Google'da kullanılır; onlar ve Bazel'da kullanılamazlar, ancak kaynak kodu buna ilişkin referanslar vardır. Bunu yöneten özelliğe constraints=

media_group() veenvironment()

Bu kurallar eski bir mekanizmadır ve yaygın olarak kullanılmamaktadır.

Tüm derleme kuralları hangi "ortamları" belirtebilir için geliştirilebilir. Örneğin, "çevre" environment() kuralının bir örneğidir.

Bir kural için desteklenen ortamlar çeşitli şekillerde belirtilebilir:

  1. restricted_to= özelliği aracılığıyla. Bu, proje yöneticisinin spesifikasyon; Kuralın desteklediği ortam grubunu tam olarak bildirir bu grup için.
  2. compatible_with= özelliği aracılığıyla. Bu işlem, ortamlara bir kural bildirir "standard"a ek olarak destekler desteklenen ortamlarda varsayılandır.
  3. Paket düzeyindeki özellikler: default_restricted_to= ve default_compatible_with=.
  4. environment_group() kurallarındaki varsayılan spesifikasyonlar aracılığıyla. Hepsini ortam, tematik olarak birbiriyle ilişkili bir grup ("CPU" gibi) mimariler", "JDK sürümleri" veya "mobil işletim sistemleri"). İlgili içeriği oluşturmak için kullanılan ortam grubu tanımı, bu ortamlardan hangisinin "default" tarafından desteklenmelidir tarafından aksi belirtilmediği takdirde restricted_to= / environment() özellikleri. Böyle bir içermeyen kural özellikleri tüm varsayılanları devralır.
  5. Kural sınıfı varsayılanı aracılığıyla. Bu, tüm cihazlar için genel varsayılanları geçersiz kılar örneklerinden birini seçin. Bu, örneğin insanların tüm *_test kurallarının, her bir örneğin açıkça test edilmesine gerek kalmadan birlikte açıklanması gerekir.

environment() normal kural olarak uygulanırken environment_group() hem Target öğesinin bir alt sınıfıdır ancak Rule (EnvironmentGroup) değildir Starlark'tan varsayılan olarak sunulan bir işlev (StarlarkLibrary.environmentGroup()) gibi bir ad verilir. hedefi belirleyebilirsiniz. Böylece ortaya çıkabilecek döngüsel bağımlılıkları önleyebilirsiniz. Çünkü ait olduğu ortam grubunu tanımlaması ve her bir ortamın ortam grubunun varsayılan ortamlarını bildirmesi gerekir.

Bir derleme, --target_environment komut satırı seçeneği.

Kısıtlama kontrolünün uygulanması RuleContextConstraintSemantics ve TopLevelConstraintSemantics.

Platform kısıtlamaları

Şu anki "resmi" hedefin hangi platformlarla uyumlu olduğunu açıklamanın araç zincirlerini ve platformları tanımlamak için kullanılan kısıtlamaların aynısını kullanmaktır. Getirme isteğinde inceleniyor #10945.

Görünürlük

Çok sayıda geliştiriciyle (Google'daki gibi) büyük bir kod tabanı üzerinde çalışıyorsanız kendi isteklerinize bağlı olarak başkalarının istedikleri girin. Aksi takdirde, Hyrum yasası uyarınca kullanıcılar sizin uyguladığınız şekilde davranacak bolca fırsat sunuyor.

Bazel bunu görünürlük adı verilen mekanizmayla destekler: Tanımlayabileceğiniz gibi, belirli bir hedefe yalnızca görünürlük özelliğine sahip olmalıdır. Bu özellik biraz özeldir çünkü bir etiket listesi içermesine rağmen, etiketler, herhangi bir öğeye bir işaretçi yerine paket adları üzerinde bir kalıbı kodlayabilir olduğunu varsayalım. (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. O da Sabit (tamamen herkese açık veya tamamen gizli) ya da bir etiketler listesi olmalıdır.
  • Etiketler, iki paket grubuna (önceden tanımlanmış paket listesi) veya doğrudan paketler (//pkg:__pkg__) veya paketlerin alt ağaçları (//pkg:__subpackages__). Bu, komut satırı söz diziminden farklıdır. //pkg:* veya //pkg/... kullanır.
  • Paket grupları kendi hedefleri (PackageGroup) olarak uygulanır ve yapılandırılmış hedef (PackageGroupConfiguredTarget). Muhtemelen bunları basit kurallarla değiştirebiliriz. Mantıklarını uyguladılar yardımcı olacak: PackageSpecification, toplamda değeri tek kalıp (ör. //pkg/...); PackageGroupContents değerine karşılık gelir tek bir package_group packages özelliğine; ve PackageSpecificationProvider, bir package_group ve geçişli includes.
  • Görünürlük etiketi listelerinden bağımlılıklara dönüşüm DependencyResolver.visitTargetVisibility ve birkaç başka örnek yer.
  • Asıl kontrol ise CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility().

İç içe yerleştirilmiş setler

Yapılandırılmış bir hedef, çoğu zaman bağımlılarından bir dizi dosya toplar. kendi verilerini ekler ve küme kümesini geçişli bilgi sağlayıcıda birleştirir. aynısını yapabilir. Ö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 çalıştırılması için sınıf yolunda olması gereken .jar dosyaları derleme veya çalıştırma
  • Python kuralının geçişli kapanışındaki Python dosyaları kümesi

Bunu, örneğin List veya Set kullanarak naif bir şekilde yaparsak ikinci dereceden bellek kullanımı: N kural zinciri varsa ve her kural kendisine bir olursa 1+2+...+N koleksiyon üyemiz olur.

Bu sorunu çözmek için NestedSet Diğer NestedSet bileşenlerinden oluşan bir veri yapısıdır ve kendine ait bazı örnekler oluşturur. Böylece, yönlendirilmiş bir döngüsel grafik oluşturulur. oluşturuyoruz. Bu kurallar değiştirilemez ve üyeleri için iterasyon yapılabilir. Bizim işimiz, birden çok yineleme siparişi (NestedSet.Order): preorder (ön sipariş), postorder (son sipariş), topolojik (topolojik) (bir düğüm her zaman üst öğelerinden sonra gelir) ve "önemli değil, ancak her seferinde aynı oluyor" şeklinde görünür.

Aynı veri yapısı, Starlark'ta depset olarak adlandırılır.

Yapılar ve İşlemler

Gerçek derleme, çalıştırılması gereken bir dizi komuttan oluşur. istediği çıktıyı vermelidir. Komutlar, tablodaki komut dosyaları Action sınıfı ve dosyalar sınıfın örnekleri olarak gösterilir Artifact. Bunlar iki parçalı, yönlendirilmiş, dairesel bir grafikle düzenlenir. "eylem grafiği".

Yapılar iki tür olabilir: kaynak yapılar (kullanılabilir olanlar ve türetilmiş yapıları (bunların yeniden yürütülmesi gereken oluşturulur). Türetilmiş yapılar birden çok tür olabilir:

  1. **Normal yapılar. **Bunların güncellik durumu bilgisayar tarafından kontrol edilir onların sağlama toplamı, kısayol olarak mtime uygulaması ile; eğer varsa dosyayı saat değişmedi.
  2. Çözümlenmemiş sembolik bağlantı yapıları. Bu bilgilerin güncelliği Readlink() çağrısında bulunun. Normal yapılardan farklı olarak bu yapılar sembolik bağlantılardır. Genellikle, bir kullanıcının bazı dosyaları tek bir yerde topladığı bir çeşit arşiv dosyası olabilir.
  3. Ağaç yapıları. Bunlar tek dosyalar değil, dizin ağaçlarıdır. Onlar içindeki dosya seti ve bu dosyaların güncellikleri kontrol edilerek içerik. Bunlar TreeArtifact olarak gösterilir.
  4. Sabit meta veri yapıları. Bu yapılarda yapılan değişiklikler, yeniden inşa etmeliyiz. Bu, yalnızca derleme damgası bilgileri için kullanılır: şu anki zaman değişti diye yeniden oluşturmak.

Kaynak yapıların ağaç eserleri oluşamamasının veya ancak bu, henüz uygulamadığımız bir simgedir (ör. bir BUILD dosyasında kaynak dizine referans vermek Bazel ile ilgili uzun süredir var olan hatalarla ilgili sorunlar; bir olanak sağlayan bu tür bir uygulama BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM mülkü)

Kayda değer bir Artifact türü aracıdır. Artifact ile gösterilir. Bunlar, MiddlemanAction çıktılarıdır. Bu özellikler bazı durumlar vardı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'miz yoktur. bağımlılık kenarları, yalnızca N+M (iç içe yerleştirilmiş kümelerle değiştiriliyor)
  • 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 (bkz. Açıklama için CcCompilationContext.createMiddleman())
  • Runfiles aracıları, bir Runfiles ağacının bulunduğundan emin olmak için çıkış manifestine bağlı olmasının gerekmediğini ve her bir tek bir yapı tarafından yürütülür.

İşlemler en iyi, çalıştırılması gereken bir komut olarak kabul edilir; ve ürettiği çıktıları seçmelidir. Aşağıda belirtilen temel hususlar bileşenleri hakkında daha fazla bilgi edinin:

  • Ç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) tanımlayan notlar \

Bazı özel durumlar da vardır. Örneğin, içeriği tam olarak aynı olan bazı ipuçları vereceğim. Bunlar, AbstractAction dersinin alt sınıfıdır. İşlemlerin çoğu SpawnAction veya StarlarkAction (aynı şekilde, muhtemelen ayrı sınıflarda yapabilir) ancak Java ve C++ kendi işlem türlerine sahiptir (JavaCompileAction, CppCompileAction ve CppLinkAction).

Sonunda her şeyi SpawnAction hedefine taşımak istiyoruz. JavaCompileAction oldukça yakındır ancak C++, .d dosya ayrıştırması ve taramayı da içerir.

İşlem grafiği çoğunlukla "yerleştirilmiş" durumdadır kavramsal olarak bir eylemin yürütülmesi, ActionExecutionFunction Bir eylem grafiği bağımlılık kenarından bir Gökyüzü çerçevesi bağımlılık kenarı ActionExecutionFunction.getInputDeps() ve Artifact.key(), birkaç tane var kullanarak Skyframe kenarlarının sayısını düşük tutun:

  • Türetilen yapıların kendi SkyValue'leri yoktur. Bunun yerine Artifact.getGeneratingActionKey(), dokümanın anahtarını bulmak için kullanılır bu işlemi oluşturan
  • İç içe yerleştirilmiş setlerin kendi Skyframe anahtarı vardır.

Paylaşılan işlemler

Bazı işlemler, birden çok yapılandırılmış hedef tarafından oluşturulur; Starlark kuralları yalnızca türetilen eylemlerini bir toplu e-tabloya yerleştirmelerine izin yapılandırmalarına ve paketlerine göre belirlenen bir dizindir (ancak aynı zamanda kuralları çakışabilir), ancak Java'da uygulanan kurallar, ortaya çıkarmanıza yardımcı olabilir.

Bunun yanlış bir özellik olduğu düşünülse de bundan kurtulmak gerçekten zor. Çünkü örneğin bir kullanıcı (ör. bir kullanıcı) çalışırken yürütme süresinde dosyanın bir şekilde işlenmesi gerektiğini ve bu dosyaya (el dalgası-el dalgaları) kullanır. Bu işlem için RAM maliyeti geçerlidir: paylaşılan bir eylem örneği bellekte ayrı olarak saklanabilir.

İki işlem aynı çıkış dosyasını oluşturuyorsa tam olarak aynı olmalıdır: aynı girişlere ve aynı çıkışlara sahip olmalı ve aynı komut satırını çalıştırmalıdır. Bu denklik ilişkisi Actions.canBeShared() işlevinde uygulanmış ve ve yürütme aşamaları arasında doğrulandığından emin olun. SkyframeActionExecutor.findAndStoreArtifactConflicts() üzerinde uygulanır ve Bazel'de "global" öğe gerektiren az sayıda görünümü seçeceğiz.

Yürütme aşaması

Bu aşamada Bazel, aşağıdakileri yapmak için gereken komutlar gibi derleme işlemlerini ve çıktılar üretir.

Analiz aşamasından sonra Bazel'in yaptığı ilk şey, o ana kadar Yapıların oluşturulması gerekir. Bunun mantığı aşağıdaki gibi kodlanmıştır: TopLevelArtifactHelper; genel olarak, filesToBuild. komut satırındaki yapılandırılmış hedefler ve özel bir çıkışın içeriği "bu hedef komuttaysa" ifadesi için bir grup oluşturun bu eserleri inşa edin".

Bir sonraki adım, yürütme kökünü oluşturmaktır. Bazel, okuma kabiliyetini dosya sisteminde farklı konumlardan gelen kaynak paketleri (--package_path), tam kaynak ağacıyla yerel olarak gerçekleştirilen işlemler sağlaması gerekir. Bu SymlinkForest sınıfı tarafından ele alınır ve her hedefi not ederek çalışır ve bu kümelere sembolik bağlantılar sunan tek bir dizin ağacı oluşturmak için her paket için tek bir hedef kullanabilirsiniz. Alternatif bir yöntem de (--package_path dikkate alınarak) komutlara giden doğru yolların iletilmesini gerektirir. Bu istenmeyen bir durumdur çünkü:

  • Bir paket, paket yolundan taşındığında işlem komut satırlarını değiştirir başka bir kullanıcıya giriş (eskiden yaygın olarak karşılaşılan bir durumdu)
  • Bir işlem, uzaktan çalıştırıldığında farklı komut satırlarına neden olur. yerel olarak çalıştırılıyor
  • Kullanılmakta olan araca özel bir komut satırı dönüşümü gerektirir. (Java sınıf yolları ile C++ içeren 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

Sonra Bazel eylem grafiğinde (iki taraflı, yönlendirilmiş grafik) işlemler ile bunların giriş ve çıkış yapıları) ve çalışan işlemlerden oluşur. Her işlemin yürütülmesi SkyValue öğesinin bir örneğiyle gösterilir ActionExecutionValue sınıfı.

Bir işlemi çalıştırmak pahalı olduğundan, işlemi gerçekleştirebilecek birkaç önbelleğe alma katmanımız Skyframe'in arkasına vurulacak:

  • ActionExecutionFunction.stateMap, Skyframe'in yeniden başlatılmasını sağlayacak veriler içeriyor / ActionExecutionFunction ucuz
  • 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 eylem de tekrar çalıştırıldığında yerel işlem önbelleğinde isabet olabilir. Google temsil eder. Bu, yerel dosya sisteminin durumunu gösterir ve yeni bir Bazel sunucusu başlatıldığında yerel işlem önbelleği isabetler içeren bir ekran görüntüsüdür.

Bu önbellek, yöntem kullanılarak isabetler açısından kontrol edilir ActionCacheChecker.getTokenIfNeedToExecute()

Adının aksine, türetilmiş bir eserin izlediği yoldan harekete geçiriyor. İşlem şu şekilde açıklanır:

  1. Giriş ve çıkış dosyaları ile bunların sağlama toplamı
  2. Bu genellikle yürütülen komut satırı olan "işlem tuşu"dur ancak genel olarak, giriş dosyaları (örneğin FileWriteAction için, verilerin sağlama toplamıdır) yazın)

Ayrıca oldukça deneysel bir "yukarıdan aşağıya işlem önbelleği" vardır hâlâ altında gibi geçişli karmalar kullanan geliştirme yöntemidir. kez.

Giriş keşfi ve giriş ayıklama

Bazı işlemler sadece bir dizi girdiye sahip olmaktan daha karmaşıktır. Şu değişiklikler var: bir eyleme yönelik girdiler kümesi iki biçimde olur:

  • Bir eylem, yürütmeden önce yeni girişler keşfedebilir veya ve girdilere ihtiyacı yoktur. Standart örnek C++, C++ içeren üstbilgi dosyalarına ilişkin bilgiye dayalı bir tahminde bulunmanın daha iyi olduğu dosyanın geçişli kapanmasından yararlanır. Böylece, her iletebilir; Bu nedenle, her bir müşterinin başlık dosyasını "giriş" olarak görüntüler ancak kaynak dosyayı geçişli olarak tarama ve bu üstbilgi dosyalarını yalnızca #include ifadelerinde bahsedilen ( tam C ön işlemcisi kullanmanız gerekir) Bu seçenek şu an için "yanlış" olarak kullanılır ve yalnızca Google'da kullanılmaktadır.
  • Bir işlem, yürütülürken bazı dosyaların kullanılmadığını fark edebilir. İçinde buna ".d dosyaları" adı verilir; derleyici, hangi başlık dosyalarının ve daha kötüsü artımlılığından bahsediyor. Bazel bu gerçeği kullanıyor. Bu sayede, tahmine dahil edilir çünkü derleyiciye dayanır.

Bunlar, İşlem yöntemleri kullanılarak uygulanır:

  1. Action.discoverInputs() çağrıldı. İç içe yerleştirilmiş Gerekli olduğu belirlenen yapılar. Bunlar kaynak yapılar olmalıdır Böylece eylem grafiğinde eşdeğerdir.
  2. İşlem, Action.execute() çağrısı yapılarak gerçekleştirilir.
  3. Action.execute() sonunda, işlem şunu çağırabilir: Bazel'a tüm girişlerinin olmadığını bildirmek için Action.updateInputs() gerekir. Bu durum, kullanılan giriş kullanılmadığı bildirildi.

Bir işlem önbelleği yeni bir Action örneğinde (ör. oluşturulan yeniden başlatıldıktan sonra) Bazel updateInputs() öğesini çağırır. Böylece girdileri daha önce yapılan keşif ve ayıklama işlemlerinin sonucunu yansıtır.

Starlark eylemleri, bazı girişleri kullanılmadığını bildirmek için bu olanağı kullanabilir şunun unused_inputs_list= bağımsız değişkeni kullanılıyor: ctx.actions.run().

İş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, ancak çeşitli korumalı alanlarda veya uzaktan yürütülür. İlgili içeriği oluşturmak için kullanılan içeren bir kavrama ActionContext (ya da Strategy olarak adlandırılır) yeniden adlandırma işleminin yalnızca yarısına kadar tamamlandı...)

Bir işlem bağlamının yaşam döngüsü aşağıdaki gibidir:

  1. Yürütme aşaması başladığında BlazeModule örneğe ne sorulur? bağlamları hakkında bilgi edindiniz. Bu, ExecutionTool İşlem bağlamı türleri Java Class ile tanımlanır ActionContext alt arayüzünü ifade eden ve aynı zamanda işlem bağlamının uygulaması gereken arayüze bağlıdır.
  2. Uygun işlem bağlamı, mevcut olanlardan seçilir ve ActionExecutionContext ve BlazeExecutor adreslerine yönlendirildi .
  3. İşlemler, ActionExecutionContext.getContext() ve kullanarak bağlamları ister. BlazeExecutor.getStrategy() (arama yaparken bu...)

Stratejiler, görevlerini yerine getirmek için başka stratejilerden yararlanabilir. bunun için, Eylemleri hem yerel olarak hem de uzaktan başlatan dinamik stratejide ve ardından hangisi önce biteni kullanır.

Kayda değer stratejilerden biri de kalıcı işçi süreçlerinin uygulanmasıdır. (WorkerSpawnStrategy). Burada bazı araçlarda başlangıç süresinin uzun olduğu düşünülür. Bu nedenle, aynı işlem için yeniden bir işlem yapmak yerine işlemler arasında yeniden her işlem (Bu durum, olası bir doğruluk sorununu temsil eder, çünkü Bazel işçi sürecinin gözlemlenebilir öğeler içermemesi yönündeki taahhüdüne dayanır aynı durumu ifade eder)

Araç değişirse çalışan işleminin yeniden başlatılması gerekir. Çalışan olup olmadığı değeri, kullanılan araç için sağlama toplamı hesaplanarak belirlenir. WorkerFilesHash Bu, eylemin hangi girişlerinin temsil ettiğini bilmek ve girişleri temsil eden bir bölümü vardır. içerik üretici tarafından belirlenir. Spawn.getToolFiles() işlemi ve Spawn çalıştırma dosyaları bir araç olarak görür.

Stratejiler (veya işlem bağlamları) hakkında daha fazla bilgi edinin:

  • İşlem çalıştırmaya yönelik çeşitli stratejilerle ilgili bilgiler mevcuttur burada bulabilirsiniz.
  • Hem işlem gerçekleştirdiğimiz dinamik stratejiyle ilgili bilgiler hangisinin önce gerçekleştiğini görmek için yerel olarak ve uzaktan burada bulabilirsiniz.
  • İşlemleri yerel olarak yürütmenin zorlukları hakkında bilgi edinebilirsiniz burada bulabilirsiniz.

Yerel kaynak yöneticisi

Bazel birçok işlemi paralel olarak çalıştırabilir. Gerçekleştirdiğiniz yerel işlemlerin sayısı, paralel olarak çalıştırılması gerekir, işlemden eyleme farklılık gösterir: bir proje için ne kadar fazla daha az örneğin aynı anda çalışması gerekir. Aksi takdirde, yerel makineye aşırı yüklenmeye başlar.

Bu, ResourceManager sınıfında uygulanır: Her işlem ihtiyaç duyulan yerel kaynakların bir tahminini içeren ResourceSet örneği (CPU ve RAM). Sonra işlem bağlamları bir şey yaptığında yerel kaynaklar gerektiren işletmeler için ResourceManager.acquireResources() ve gerekli kaynaklar mevcut olana kadar engellenir.

Yerel kaynak yönetimiyle ilgili daha ayrıntılı bir açıklama mevcut burada bulabilirsiniz.

Çıkış dizininin yapısı

Her işlem, çıkış dizininde, ve çıkarımlarda bulunması gerekir. Türetilen yapıların konumu genellikle şu şekildedir:

$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>

Belirli bir web sitesiyle ilişkilendirilen dizinin adı nasıl belirlenir? Çakışan istenen iki özellik vardır:

  1. Aynı derlemede iki yapılandırmanın oluşabileceği durumlarda, Böylece her iki dizinin de kendi sürümüne sahip olması gerekir eylem; aksi takdirde, iki yapılandırma arasında hemfikir değilse (örneğin, işlemini oluşturan bir eylem satırı oluşturmak için, Bazel hangi seçilecek işlem ("eylem çakışması")
  2. İki yapılandırma "kabaca" ifadesini temsil ediyorsa aynı şeyi düşünmeleri aynı ada sahip olduğundan, birinde yürütülen işlemlerin diğeri için yeniden kullanılabilmesi komutu satırlarıyla eşleşir: örneğin, komut satırı seçeneklerinde yapılan değişiklikler Java derleyicisi, C++ derleme işlemlerinin yeniden çalıştırılmasına neden olmamalıdır.

Şu ana kadar bu sorunu çözmek için prensipli bir yöntem bulamadık. yapılandırma kırpma sorunuyla benzerliklere sahiptir. Daha uzun bir tartışma kullanılabilir bir seçenek var burada bulabilirsiniz. Başlıca sorunlu alanlar Starlark kurallarıdır (yazarlar genellikle (Bazel hakkında bilgi sahibi olan) ve yönleriyle "aynı" şeyi üretebilen şeylerin alanı çıktı dosyası olarak kaydedebilirsiniz.

Mevcut yaklaşım, yapılandırmanın yol segmentinin Yapılandırmanın düzgün çalışması için çeşitli sonekler eklenmiş <CPU>-<compilation mode> geçişler, işlem çakışmalarına neden olmaz. Ayrıca, Starlark yapılandırma geçişleri kümesinin sağlama toplamı, kullanıcıların işlem çakışmalarına neden olamaz. Bu yöntem mükemmel değildir. Uygulandığı yer: OutputDirectories.buildMnemonic() ve her bir yapılandırma parçasına dayanır çıkış dizininin adına kendi parçasını ekleyerek.

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 (detay oluşturmak veya zamanlamayı toplamak için) verileri)
  • Testleri parçalama (aynı testteki test durumlarını birden çok işlem üzerinde bölme) için)
  • Güvenilir olmayan testleri yeniden çalıştırma
  • Testleri test paketleri halinde gruplandırma

Testler, bir TestProvider'ı içeren normal yapılandırılmış hedeflerdir. testin nasıl çalıştırılması gerektiği:

  • Yapısı sonucunda testin çalıştırıldığı yapılar. Bu bir "önbellek"tir durum" serileştirilmiş TestResultData mesajı içeren dosya
  • 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. İlgili içeriği oluşturmak için kullanılan genişletme TestsForTargetPatternFunction uygulamasında uygulandı. Biraz şaşırtıcı kırışıklık ise bir test paketinin test olmadığını beyan etmesi durumunda, her testi kendi paketine ekler. Bu, Package.beforeBuild() ürününde şunun tarafından uygulanır: paket kurallarını test etmek için $implicit_tests adlı örtülü bir özellik ekleyerek.

Ardından, testler komut satırı seçenekleri. Bu, TestFilter üzerinde uygulandı ve ve hedef ayrıştırma sırasında TargetPatternPhaseFunction.determineTests() sonuç TargetPatternPhaseValue.getTestsToRunLabels() içine yerleştirilir. Neden filtrelenebilecek kural özelliklerinin yapılandırılamamasının nedeni, aşamasından önce gerçekleştiğinden yapılandırma, kullanılabilir.

Bu daha sonra BuildView.createResult() içinde daha fazla işlenir: başarısız olan analizler filtrelenir ve testler özel ve münhasır olmayan testler olabilir. Daha sonra AnalysisResult bölümüne yerleştiriliyor. ExecutionTool hangi testlerin çalıştırılacağını biliyor.

tests(), bu ayrıntılı sürece biraz şeffaflık kazandırmak için sorgu operatörü (TestsFunction dilinde uygulanır) hangi testlerin komut satırında belirli bir hedef belirtildiğinde çalıştırılır. İnsanların bu nedenle de büyük olasılıkla yukarıdakilerden farklı bir uygulamadır. pek çok farklı yöntem vardır.

Testler yapılıyor

Testlerin çalıştırılma şekli, önbellek durumu yapıları istemektir. Bu ardından bir TestRunnerAction yürütülmesiyle sonuçlanır ve sonuçta --test_strategy komut satırı seçeneği tarafından seçilen TestActionContext testi istenen şekilde çalıştırır.

Testler, ortam değişkenlerini kullanan ayrıntılı bir protokole göre yürütülür ve testlere onlardan ne beklendiğini anlatmayı unutmayın. Bazel'in ne yaptığını ayrıntılı şekilde kullanılabilirliği hakkında daha fazla bilgi için burada bulabilirsiniz. Şurada: en basit olanı, 0 çıkış kodunun başarılı olduğu, diğer her şey ise başarısız olduğu anlamına gelir.

Önbellek durum dosyasına ek olarak, her test işlemi bir dizi başka örnek dosyası olarak da kaydedebilir. Bu dosyalar "test günlüğü dizinine" yerleştirilir. "Bu sayfa, Google'ın açık kaynak Hedef yapılandırmanın çıkış dizininin testlogs kadarı:

  • test.xml, her test durumunu ayrıntılı bir şekilde belirten JUnit stili bir XML dosyasıdır test kırığı
  • test.log, testin konsol çıktısı. stdout ve stderr değildir olabilir.
  • test.outputs, "bildirilmemiş çıkışlar dizini"; bu, testler tarafından terminalde yazdırdıklarına ek olarak dosyaların çıkışını almak isteyen kullanıcılar içindir.

Test yürütülürken gerçekleşebilecek iki şey vardır. düzenli hedefler oluşturma: özel test yürütme ve çıkış akışı.

Bazı testlerin özel modda (örneğin, başka testler de var. Bu,tags=["exclusive"] test kuralını uygulayın veya testi --test_strategy=exclusive ile çalıştırın . Her biri özel test, "ana" koddan sonra test etmek seçeceğiz. Uygulandığı yer: SkyframeExecutor.runExclusiveTest()

İşlem sırasında terminal çıkışı dökümü alınan normal işlemlerin aksine kullanıcı testlerin sonucunun akış olarak akmasını isteyebilir. Böylece, ve uzun süreli bir testin ilerleme durumu hakkında bilgilendirilecek. Bu, --test_output=streamed komut satırı seçeneği ve özel test anlamına gelir Böylece, farklı testlerin çıktıları karışıklığa neden olmaz.

Bu, uygun şekilde adlandırılmış StreamedTestOutput sınıfında uygulanır ve söz konusu testin test.log dosyasında yapılan yoklama değişiklikleri ve yeni döküm bayt olarak aktarması gerekir.

Yürütülen testlerin sonuçları çeşitli etkinlikler (TestAttempt, TestResult veya TestingCompleteEvent gibi). Bunlar, Derleme Etkinliği Protokolü'ne atanır ve konsola iletilir AggregatingTestListener tarafından.

Kapsam koleksiyonu

Kapsam, testler tarafından dosyalardaki LCOV biçiminde bildirilir. bazel-testlogs/$PACKAGE/$TARGET/coverage.dat

Kapsamın toplanması için her test yürütmesi, collect_coverage.sh

Bu komut dosyası, test ortamını kapsam toplamayı etkinleştirecek şekilde ayarlar ve kapsam dosyalarının kapsam çalışma zamanlarına göre nereye yazıldığını belirler. Daha sonra testi çalıştırır. Bir testin kendisi birden fazla alt işlem yürütebilir ve birden fazla programlama dilinde yazılmış bölümlerin sayısı (ayrı ayrı kapsam koleksiyonu çalışma zamanları) gösterilir. Sarmalayıcı komut dosyası, oluşturulan bu dosyaları, gerekirse LCOV biçiminde olur ve tek bir dosyası olarak kaydedebilirsiniz.

collect_coverage.sh arasındaki konum, test stratejileri tarafından yapılır ve collect_coverage.sh öğesinin test girişlerinde olmasını gerektirir. Bu ,:coverage_support --coverage_support yapılandırma işaretinin değeri (bkz. TestConfiguration.TestOptions.coverageSupport)

Bazı diller çevrimdışı enstrümantasyonu yapar, yani kapsam enstrümantasyon, derleme sırasında eklenir (ör. C++) araçlar, yani kapsam araçları yürütme sırasında eklenir gerekir.

Diğer bir temel kavram da referans kapsamdır. Bu, bir kütüphanenin kapsamı, veya içinde hiç kod çalıştırılıp çalıştırılmadığını test edebilirsiniz. Çözdüğü sorun, herhangi bir sorun test kapsamını hesaplamak istiyorsanız tüm testlere dahildir çünkü ikili dosyada teste bağlı değildir. Dolayısıyla, yaptığımız her bir trafik için bir kapsam dosyası yalnızca kapsama dahil ettiğimiz dosyaları içeren ikili program satırlarda ilerleyin. Bir hedef için referans kapsam dosyası şu değerdedir: bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat Ayrıca, testlere ek olarak ikili programlar ve kitaplıklar için Bazel'e --nobuild_tests_only işareti.

Temel kapsam şu anda bozuk.

Her kural için kapsam koleksiyonu amacıyla iki dosya grubu izleriz: araç meta veri dosyaları kümesi için de geçerlidir.

Enstrümantasyonlu dosyalar grubu da adından da anlaşılacağı üzere bir dizi dosyadan ibarettir. Örneğin, bu değer, çalışma zamanında hangi dosyaların gösterileceğine karar vermek için enstrümanı kullanabilirsiniz. Ayrıca temel kapsamı uygulamak için de kullanılır.

Araç meta veri dosyaları grubu, testin ihtiyaç duyduğu ekstra dosya kümesidir kullanarak, Bazel'ın gerektirdiği LCOV dosyalarını oluşturabilirsiniz. Pratikte bu, çalışma zamanına özel dosyalar; örneğin, gcc, derleme sırasında .gcno dosyaları yayar. Kapsam modu etkin.

Kapsamın toplanıp toplanmadığı BuildConfiguration Bu, testi değiştirmenin kolay bir yolu olduğu için yararlıdır bağlı olarak değişiklik gösterir, ancak aynı zamanda tüm hedeflerin yeniden analiz edilmesi gerekir (örneğin, C++, kapsam toplayabilecek kod yayınlamak için farklı derleyici seçenekleri gerektirir, Bu da sorunun bir miktar azalmasına neden olur, çünkü yeniden analiz yapılması gerekir).

Kapsam destek dosyaları, örtülü bağımlılığı, böylece bunların geçersiz kılınabilmesi için çağrı politikası ve Bazel'in farklı versiyonları arasında farklılık göstermeliyiz. İdeal koşullarda bu ve bunlardan birini standartlaştırdık.

Ayrıca bir "kapsam raporu" ve bu veriler için toplanan kapsamı birleştirerek her test için kullanılamaz. Bu işlem, CoverageReportActionFactory ve BuildView.createResult() üzerinden çağrılıyor . Google :coverage_report_generator kontrol ederek ihtiyacı olan araçlara erişebilir. özelliğinin değerini döndürür.

Sorgu motoru

Bazel, küçük dil çeşitli grafiklerle ilgili çeşitli sorular sormak için kullanılır. Aşağıdaki sorgu türleri şunlar 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ır
  • bazel 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ırması yapılarak ek sorgu işlevleri yapılabilir. , Sorgu sonuçlarının akış şeklinde gösterilmesi için, sonuçları belirli QueryFunction veri yapısına bir query2.engine.Callback aktarılır ve istediği sonuçlar için çağrıyı yapar.

Sorgunun sonucu çeşitli şekillerde yayınlanabilir: etiketler, etiketler ve kural temel işlemlerdir. Bunlar, OutputFormatter

Bazı sorgu çıkış biçimlerinin (proto, kesinlikle) bir gereksinimi olan Bazel'in, paket yüklemenin sağladığı _tüm _bilgileri yayması gerekir. çıktıyı farklılık gösterebilir ve belirli bir hedefin değişip değişmediğini belirleyebilir. Sonuç olarak, özellik değerlerinin seri hale getirilebilir olması gerekir. Bu nedenle, hiçbir özelliği karmaşık Starlark'a sahip olmayan çok az sayıda özellik türü olduğundan değerler. Genellikle çözüm, bir etiket kullanmak ve karmaşıklığı bilgisi de ekler. Pek tatmin edici bir çözüm değil bu şartın kaldırılması çok faydalı olur.

Modül sistemi

Bazel, üzerine modül eklenerek genişletilebilir. Her modülün alt sınıfları BlazeModule (ad, eskiden Bazel'in tarihine ait bir emanettir. adı verilen ve Blaze) yürütülürken çeşitli olaylar hakkında bilgi edinir komut verebilirsiniz.

Çoğunlukla "temel olmayan" çeşitli öğelerin uygulanması için kullanılırlar işlevsellik yalnızca bazı Bazel sürümlerinde (Google'da kullandığımız sürüm gibi) şunlara ihtiyaç vardır:

  • Uzaktan yürütme sistemlerine yönelik arayüzler
  • Yeni komutlar

BlazeModule tarafından sunulan uzantı noktaları grubu rastgele. Şunları Yapmayın: iyi tasarım ilkelerine örnek olarak gösterilebilir.

Etkinlik otobüsü

BlazeModules'un Bazel'in geri kalanıyla iletişim kurmak için kullandığı ana yöntem etkinlik otobüsü (EventBus): Bazel'in çeşitli bölümleri olan her derleme için yeni bir örnek oluşturulur bu bölümde etkinlik yayınlayabilir ve modüller, dinleyicileri emin olabilirsiniz. Ö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ı Bazel dışındaki Etkinlik Protokolü Oluşturma (bunlar BuildEvent). Bu sadece BlazeModule değil, aynı zamanda şeylere de izin verir. Bazel sürecinin dışında kalan kod bulunur. Bunlara veya Bazel'in bir sunucuya bağlanabildiğini (ör. (Derleme Etkinliği Hizmeti) oluşturun.

Bu, build.lib.buildeventservice ve build.lib.buildeventstream Java paketleri.

Harici depolar

Bazel aslen bir monorepoda (tek bir kaynak olarak) bir ağaç kıyafeti için gereken her şeyi içeren bir ağaç varsa, Bazel bu her zaman doğru değildir. "Harici depolar" bir soyutlama olan arasında bağlantı oluşturur. Bunlar derleme için gerekli olan kodu temsil eder, ana kaynak ağacında değildir.

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 ne anlama geliyor? Starlark dosyalarında yeni depo kuralları tanımlanabilmesi, sonra yeni Starlark kodunu yüklemek üzere kullanılabilir. Bu kod, depo kuralları ve benzerleri...

Bu durumu ele almak için WORKSPACE dosyasının ayrıştırılması ( WorkspaceFileFunction), load() ile açıklanan parçalara ayrılır açıklamalarına dikkat edin. Parça dizini WorkspaceFileKey.getIndex() ve X dizinine kadar WorkspaceFileFunction hesaplama, o zamana kadar X. load() ifadesi.

Kod depoları getiriliyor

Kod deposunun kodu Bazel'in kullanımına sunulmadan önce, getirildi. Bu, Bazel'in $OUTPUT_BASE/external/<repository name>

Kod deposunu getirme işlemi aşağıdaki adımlarla gerçekleşir:

  1. PackageLookupFunction, bir depoya ihtiyacı olduğunu anlar ve bir RepositoryLoaderFunction çağıran SkyKey olarak RepositoryName
  2. RepositoryLoaderFunction, isteği şu adrese yönlendirir: Belirsiz nedenlerle RepositoryDelegatorFunction (kodda yeniden indirilmemesi durumunda bir şeyleri yeniden indirmekten kaçının ancak bu, gerçekten çok sağlam bir gerekçe)
  3. RepositoryDelegatorFunction, istenen depo kuralını öğrenir istenene kadar WORKSPACE dosyasının parçaları üzerinde yineleme yaparak getirme depo bulundu
  4. Depoyu uygulayan uygun RepositoryFunction bulundu fetching; deponun Starlark uygulaması veya bir için sabit kodlu bir harita oluşturur.

Depo getirmek çok zor olabileceği için, önbelleğe almanın çeşitli katmanları vardır pahalı:

  1. İndirilen dosyalar için sağlama toplamıyla girilen bir önbellek vardır (RepositoryCache). Bu, sağlama toplamının WORKSPACE dosyasıdır, ancak bu hermetik özellik için iyidir. Bu, şu kullanıcı tarafından paylaşılıyor: aynı iş istasyonundaki her Bazel sunucu örneği; çalışma alanı veya çıktı tabanını oluşturur.
  2. "İşaretçi dosyası" $OUTPUT_BASE/external altındaki her depo için yazılır kuralının sağlama toplamını içeren bir veri kümesi içerir. Bazel sunucu yeniden başlatılır, ancak sağlama toplamı değişmez ve yeniden getirilmez. Bu RepositoryDelegatorFunction.DigestWriter dilinde uygulandı .
  3. --distdir komut satırı seçeneği, indirilecek yapıları arayın. Bu özellik, kurumsal ayarlarda kullanışlıdır Burada Bazel'in internetten rastgele öğeler getirmemesi gerekir. Bu DownloadManager tarafından uygulandı .

Depo indirildikten sonra içindeki yapılar kaynak olarak değerlendirilir olabilir. Bazel genellikle güncelleme olup olmadığını kontrol ettiğinden bu durum bir sorun teşkil eder. stat() çağrısı yaparak kaynak yapıların üzerinde çalışır. Bu yapılar ayrıca, deponun tanımı değiştiğinde geçersiz kılınır. Böylece, Harici bir depodaki bir yapı için FileStateValue öğeleri depolarına gönderebilirsiniz. Bu işlem ExternalFilesHelper tarafından işlenecek.

Yönetilen dizinler

Bazen harici depoların, çalışma alanı kökü altındaki dosyaları değiştirmesi gerekir (örneğin, indirilen paketleri web sitesinin bir alt dizininde barındıran bir paket yöneticisi gibi) kaynak ağacı). Bu durum, Bazel'in o kaynağı kendiliğinden yediği varsayımıyla dosyalar yalnızca kullanıcı tarafından değiştirilir, kendiliğinden değiştirilmez ve paketlerin çalışma alanı kökü altındaki her dizine referansta bulunur. Bu tür bir proje için için iki şey yapar:

  1. Kullanıcının, Bazel'in yapmadığı çalışma alanının alt dizinlerini belirtmesine olanak tanır izin verildiğini göreceksiniz. Bunlar, .bazelignore adlı bir dosyada listelenir ve işlev BlacklistedPackagePrefixesFunction içinde uygulanmıştır.
  2. Eşlemeyi çalışma alanının alt dizininden harici tarafından işlendiği depo, ManagedDirectoriesKnowledge ve işleyici FileStateValue, normal abonelerle aynı şekilde emin olun.

Depo eşlemeleri

Birden fazla depo aynı depoya bağımlı olabilir. Ancak farklı versiyonlarda (bu, "elmas bağımlılığının sorun"). Örneğin, derlemede ayrı depolardaki iki ikili program ikisi de muhtemelen Gava'yı işaret eden plak şirketiyle @guava// tarihinden itibaren geçerli olacak ve bu metriğin farklı sürümleri anlamına gelmesini bekleyebilirsiniz.

Bu nedenle Bazel, harici depo etiketlerinin yeniden eşlenmesine izin verir. Böylece, @guava// dizesi,@guava1// bir ikili veri deposu ve başka bir Guava deposu (@guava2// gibi) diğerinin depoları olabilir.

Alternatif olarak bu, elmasları birleştirmek için de kullanılabilir. Depo, @guava1// veri deposuna bağlıdır, diğeri ise @guava2// veri deposu eşlemesine bağlıdır bir standart @guava// deposu kullanmak için her iki deponun da yeniden eşlenmesine olanak tanır.

Eşleme, WORKSPACE dosyasında repo_mapping özelliği olarak belirtilir tanımlarını öğreneceksiniz. Ardından Skyframe'de şunun üyesi olarak görünür: WorkspaceFileValue, enerjinin kaynaklandığı yer:

  • Etiket değerli dönüşümünü dönüştürmek için kullanılan Package.Builder.repositoryMapping özelliklerini RuleClass.populateRuleAttributeValues()
  • Analiz aşamasında kullanılan Package.repositoryMapping ( yükleme sırasında ayrıştırılmayan $(location) gibi sorunları çözümleme aşamasında)
  • load() ifadelerindeki etiketleri çözümlemek için BzlLoadFunction

JNI bitleri

Bazel sunucusu genellikle Java dilinde yazılmıştır. Ancak bir istisna olan Java kendi başına yapamadığı gibi, uygulandığı zaman da kendi başına yapamıyor. Bu çoğu zaman dosya sistemi, işlem denetimi ve süreç düşük seviyeli başka birçok şey olabilir.

C++ kodu, src/main/native, yerel kod içeren Java sınıfları altında bulunur. yöntemleri şunlardır:

  • NativePosixFiles ve NativePosixFileSystem
  • ProcessUtils
  • WindowsFileOperations ve WindowsFileProcesses
  • com.google.devtools.build.lib.platform

Konsol çıkışı

Konsoldan çıkış yapma gibi basit bir iş gibi görünse de birden fazla işlem (bazen uzaktan), ayrıntılı önbelleğe alma, güzel ve renkli bir terminal çıkışı vardır ve uzun süreli bir sunucuya sahip olmak sıradan bir iş değildir.

İstemciden gelen TBG çağrısının hemen ardından iki RpcOutputStream stdout ve stderr için, örneği hazırlayıp müşteriye anlatır. Bunlar daha sonra bir OutErr (stdout, stderr) içine alınır. çifti) ekleyebilirsiniz. Konsolda yazdırılması gereken her şey bu belgelerden geçer akışlar. Ardından bu canlı yayınlar, BlazeCommandDispatcher.execExclusively()

Çıktı, varsayılan olarak ANSI çıkış sıralarıyla yazdırılır. Bunlar ne zaman --color=no) AnsiStrippingOutputStream ile çıkarılır. İçinde Ayrıca System.out ve System.err bu çıkış akışlarına yönlendiriliyor. Böylece hata ayıklama bilgileri System.err.println() ve hâlâ istemcinin terminal çıkışında yer alıyor (sunucudan farklıdır). Bir işlemin ikili çıktı (bazel query --output=proto gibi) üretir, stdout ile uğraşmaz yaşandığını söylüyor.

Kısa mesajlar (hatalar, uyarılar vb.) EventHandler arayüzü. Bunların yayınlardan farklı EventBus (bu kafa karıştırıcıdır). Her Event için bir EventKind var (hata, uyarı, bilgi ve birkaç kişi daha) varsa Location (Şuradaki yer) (etkinliğin gerçekleştirilmesine neden olan kaynak kodu).

Bazı EventHandler uygulamaları, aldıkları etkinlikleri depolar. Kullanılan çeşitli önbellek işleme yöntemlerinin neden olduğu kullanıcı arayüzünde bilgileri tekrar oynatmak için örneğin, önbelleğe alınmış yapılandırılmış bir hedef tarafından yayınlanan uyarılar.

Bazı EventHandler'lar, sonunda yolu olan etkinliklerin yayınlanmasına da olanak tanır. etkinlik otobüsü (normal Event'ler orada _görünmez _gibi) görünür. Bunlar: ExtendedEventHandler uygulamaları ve bunların temel kullanımı, önbelleğe alınan içeriği tekrar oynatmaktır EventBus etkinlik. Bu EventBus etkinliklerinin tümü Postable uygular ancak uygulamaz EventBus adresine gönderilen her şey, mutlaka bu arayüzü uygular; yalnızca bir ExtendedEventHandler tarafından önbelleğe alınanlar (kullanılabilir çoğu şeyi yapıyor; Ancak bu yaptırım uygulanmaz)

Terminal çıkışı çoğunlukla UiEventHandler üzerinden yayınlanır. tüm gösterişli çıktı biçimlendirmesi ve ilerleme raporlamasından sorumlu Bazel. yapıyor. İki girişi vardır:

  • Etkinlik otobüsü
  • Rapor eden aracılığıyla eklenen etkinlik akışı

Komut çalıştırma makinelerinin tek doğrudan bağlantısıdır (örneğin, Bazel), istemciye giden RPC akışını Reporter.getOutErr() üzerinden yapıyor. Bu da söz konusu akışlara doğrudan erişim sağlar. Yalnızca bir komut için büyük miktarlarda olası ikili veri (bazel query gibi) dökümünü almak için kullanılır.

Bazel Profil Oluşturma

Bazel hızlı. Bazel de yavaştır çünkü derlemeler yalnızca her açıdan bir göze çarpar. Bu nedenle, Bazel profil binaları ve Bazel'in kendisi için kullanılır. Uygulanmış olan sınıfa uygun şekilde Profiler olarak adlandırıldı. Varsayılan olarak açıktır, ancak yalnızca kayıt kısaltılmış olmalıdır. Komut satırı --record_full_profiler_data, yapabildiği 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örevleri başlatıp bitirebilir ve birbirlerine düzgün bir şekilde yerleştirilmeleri gerekir. Her bir Java iş parçacığı görevi görebilir. YAPILACAKLAR: Bu özellik, işlemleri ve nasıl oluşturabilirsiniz?

Profil oluşturucu BlazeRuntime.initProfiler() içinde başlatıldı ve durduruldu Sırasıyla BlazeRuntime.afterCommand() ve süre boyunca yayında kalmaya çalışır Böylece her şeyin profilini çıkarabiliriz. Profile öğe eklemek için: Profiler.instance().profile() numaralı telefonu arayın. Kapanışı olan bir Closeable döndürüyor görevin sonunu temsil eder. Deneme amaçlı kaynaklarla kullanılması daha uygundur açıklamalarına dikkat edin.

MemoryProfiler ürününde de temel bellek profili oluşturma işlemi gerçekleştiriyoruz. Ayrıca her zaman açık ve çoğunlukla maksimum yığın boyutlarını ve GC davranışını kaydeder.

Bazel'i test etme

Bazel'in iki ana tür testi vardır: Bazel'in "kara kutu" olduğunu gösteren testler ve yalnızca analiz aşamasını gerçekleştirenler. Eski adıyla "entegrasyon testleri" "birim testleri" olarak tanımlar, ancak bu testler daha çok çok daha az entegredir. Ayrıca, kullanılan birim testlerinde gerekir.

Entegrasyon testlerinin iki türü vardır:

  1. src/test/shell.
  2. Java'da uygulananlar. Bunlar, BuildIntegrationTestCase.

BuildIntegrationTestCase, entegrasyon testi çerçevesi olduğundan tercih edilen çoğu test senaryosuna uygun donanıma sahiptir. Bu bir Java çerçevesi olduğundan birçok yaygın geliştirme ile hata ayıklanabilirlik ve sorunsuz entegrasyon sağlar araçlar. BuildIntegrationTestCase sınıfına ilişkin birçok örnek Bazel deposu.

Analiz testleri, BuildViewTestCase alt sınıfları olarak uygulanır. Bir BUILD dosyalarını yazmak için kullanabileceğiniz ortak çalışma dosya sistemi ve ardından çeşitli yardımcılar yöntemlerin yapılandırılmış hedefleri isteme, yapılandırmayı değiştirme ve onaylama çeşitli şeylerle açıklanabilir.