Osiem agentów, jeden COM: jak naprawiliśmy build .NET w Azure DevOps
Opublikowano · 2 min czytania · Tomasz Dłuski
Pipeline miał zbudować projekt .NET Framework 4.8 z referencją COMReference. Przed kompilacją rejestrował DLL komponentu COM, a po zakończeniu ją wyrejestrowywał. Przy jednym jobie wszystko się zgadzało. Klient miał jednak osiem agentów Azure DevOps na jednej VM. Wystarczyły dwa równoległe joby, żeby doszło do wyścigu.
Osobne katalogi, wspólny rejestr
Agenty miały własne katalogi robocze, ale rejestracja komponentu była wspólna dla maszyny. COMReference rozwiązywał zależność przez rejestr Windows, więc stan przygotowany przez jeden job wpływał na drugi. W naszym skrypcie istniejąca rejestracja wystarczała, żeby drugi job przeszedł dalej. Na przykładzie dwóch z ośmiu agentów wyglądało to tak:
Jedna VM, wspólny rejestr
Najedź na element, aby zobaczyć szczegóły.
Przed: wspólna zależność
Agent A
checkout A
Build OK → cleanup
Wyrejestrowuje COM
Rejestr COM
jeden wpis dla całej VM
Wpis usunięty przez A
Agent B
checkout B
Build failed
Nie da się rozwiązać COMReference
A sprząta, zanim B dojdzie do kompilacji. B traci zależność i build się wywala.
Po: każdy job ma własny interop
Przygotowane wcześniej: typy COM → tlbimp.exe → assembly .NET
Agent A
checkout A
Własna kopia interopu
Cleanup usuwa tylko pliki A
Agent B
checkout B
Własna kopia interopu
Build succeeded
Schemat dotyczy kompilacji. Aplikacja nadal potrzebuje COM przy uruchomieniu.
Samo skasowanie DLL nie wyrejestrowuje COM: może zostawić wpis wskazujący nieistniejący plik. U nas problemem był cały cykl przygotowania i sprzątania współdzielonej zależności. To, że komponent istniał na początku joba B, nie gwarantowało jego dostępności podczas kompilacji.
Rozwiązanie: biblioteka interop
Wydzieliliśmy warstwę interop do osobnej biblioteki interop.comproject.csproj. Główny projekt dostał zwykłą zależność .NET do tej warstwy. Jego build przestał wymagać rejestrowania i wyrejestrowywania COM na współdzielonym agencie.
Wrapper wygenerowaliśmy za pomocą tlbimp.exe na podstawie biblioteki typów komponentu COM. Powstałe assembly .NET dostarcza typy interop potrzebne do kompilacji. Build aplikacji korzysta z gotowej biblioteki, zamiast rozwiązywać COMReference przez rejestr współdzielonej VM.
Dzięki temu cleanup jednego joba przestał podcinać kompilację drugiego. Zależność od samego komponentu COM podczas uruchamiania aplikacji pozostała osobną sprawą.