Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps

Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps
Συνεχίζουμε να κάνουμε τη χρήση του PVS-Studio πιο βολική. Ο αναλυτής μας είναι πλέον διαθέσιμος στο Chocolatey, έναν διαχειριστή πακέτων για Windows. Πιστεύουμε ότι αυτό θα διευκολύνει την ανάπτυξη του PVS-Studio, ιδίως σε υπηρεσίες cloud. Για να μην πάμε μακριά, ας ελέγξουμε τον πηγαίο κώδικα του ίδιου Chocolatey. Το Azure DevOps θα λειτουργεί ως σύστημα CI.

Ακολουθεί μια λίστα με τα άλλα άρθρα μας σχετικά με το θέμα της ενοποίησης με συστήματα cloud:

Σας συμβουλεύω να δώσετε προσοχή στο πρώτο άρθρο σχετικά με την ενσωμάτωση με το Azure DevOps, καθώς σε αυτήν την περίπτωση ορισμένα σημεία παραλείπονται για να μην επαναληφθούν.

Λοιπόν, οι ήρωες αυτού του άρθρου:

PVS-Στούντιο είναι ένα εργαλείο ανάλυσης στατικού κώδικα που έχει σχεδιαστεί για τον εντοπισμό σφαλμάτων και πιθανών τρωτών σημείων σε προγράμματα γραμμένα σε C, C++, C# και Java. Εκτελείται σε συστήματα Windows, Linux και macOS 64 bit και μπορεί να αναλύσει κώδικα που έχει σχεδιαστεί για πλατφόρμες 32 bit, 64 bit και ενσωματωμένες πλατφόρμες ARM. Εάν αυτή είναι η πρώτη φορά που δοκιμάζετε στατική ανάλυση κώδικα για να ελέγξετε τα έργα σας, σας συνιστούμε να εξοικειωθείτε άρθρο για το πώς μπορείτε να δείτε γρήγορα τις πιο ενδιαφέρουσες προειδοποιήσεις PVS-Studio και να αξιολογήσετε τις δυνατότητες αυτού του εργαλείου.

Azure DevOps — ένα σύνολο υπηρεσιών cloud που καλύπτουν από κοινού ολόκληρη τη διαδικασία ανάπτυξης. Αυτή η πλατφόρμα περιλαμβάνει εργαλεία όπως Azure Pipelines, Azure Boards, Azure Artifacts, Azure Repos, Azure Test Plans, τα οποία σας επιτρέπουν να επιταχύνετε τη διαδικασία δημιουργίας λογισμικού και να βελτιώσετε την ποιότητά του.

Σοκολάτα είναι ένας διαχειριστής πακέτων ανοιχτού κώδικα για Windows. Ο στόχος του έργου είναι να αυτοματοποιήσει ολόκληρο τον κύκλο ζωής του λογισμικού από την εγκατάσταση έως την ενημέρωση και την απεγκατάσταση σε λειτουργικά συστήματα Windows.

Σχετικά με τη χρήση του Chocolatey

Μπορείτε να δείτε πώς να εγκαταστήσετε τον ίδιο τον διαχειριστή πακέτων σε αυτό σύνδεσμος. Η πλήρης τεκμηρίωση για την εγκατάσταση του αναλυτή είναι διαθέσιμη στη διεύθυνση σύνδεσμος Δείτε την Εγκατάσταση με χρήση της ενότητας Διαχείριση πακέτων Chocolatey. Θα επαναλάβω εν συντομία κάποια σημεία από εκεί.

Εντολή για την εγκατάσταση της πιο πρόσφατης έκδοσης του αναλυτή:

choco install pvs-studio

Εντολή για εγκατάσταση μιας συγκεκριμένης έκδοσης του πακέτου PVS-Studio:

choco install pvs-studio --version=7.05.35617.2075

Από προεπιλογή, εγκαθίσταται μόνο ο πυρήνας του αναλυτή, το στοιχείο Core. Όλες οι άλλες σημαίες (Standalone, JavaCore, IDEA, MSVS2010, MSVS2012, MSVS2013, MSVS2015, MSVS2017, MSVS2019) μπορούν να περάσουν χρησιμοποιώντας --package-parameters.

Ένα παράδειγμα εντολής που θα εγκαταστήσει έναν αναλυτή με ένα πρόσθετο για το Visual Studio 2019:

choco install pvs-studio --package-parameters="'/MSVS2019'"

Τώρα ας δούμε ένα παράδειγμα βολικής χρήσης του αναλυτή στο Azure DevOps.

προσαρμογή

Επιτρέψτε μου να σας υπενθυμίσω ότι υπάρχει μια ξεχωριστή ενότητα σχετικά με θέματα όπως η εγγραφή ενός λογαριασμού, η δημιουργία ενός Build Pipeline και ο συγχρονισμός του λογαριασμού σας με ένα έργο που βρίσκεται στο αποθετήριο GitHub. άρθρο. Η εγκατάσταση μας θα ξεκινήσει αμέσως με τη σύνταξη ενός αρχείου διαμόρφωσης.

Αρχικά, ας ρυθμίσουμε έναν κανόνα εκκίνησης, υποδεικνύοντας ότι εκκινούμε μόνο για αλλαγές στο κύριος κλαδί:

trigger:
- master

Στη συνέχεια πρέπει να επιλέξουμε μια εικονική μηχανή. Προς το παρόν θα είναι ένας πράκτορας που θα φιλοξενείται από τη Microsoft με Windows Server 2019 και Visual Studio 2019:

pool:
  vmImage: 'windows-latest'

Ας προχωρήσουμε στο σώμα του αρχείου διαμόρφωσης (μπλοκ βήματα). Παρά το γεγονός ότι δεν μπορείτε να εγκαταστήσετε αυθαίρετο λογισμικό σε μια εικονική μηχανή, δεν πρόσθεσα ένα κοντέινερ Docker. Μπορούμε να προσθέσουμε το Chocolatey ως επέκταση για το Azure DevOps. Για να το κάνουμε αυτό, ας πάμε στο σύνδεσμος. Κάντε κλικ Αποκτήστε το δωρεάν. Στη συνέχεια, εάν είστε ήδη εξουσιοδοτημένοι, απλώς επιλέξτε τον λογαριασμό σας και αν όχι, κάντε το ίδιο μετά την εξουσιοδότηση.

Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps

Εδώ πρέπει να επιλέξετε πού θα προσθέσουμε την επέκταση και να κάνετε κλικ στο κουμπί εγκαταστήστε.

Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps

Μετά την επιτυχή εγκατάσταση, κάντε κλικ Προχωρήστε στην οργάνωση:

Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps

Τώρα μπορείτε να δείτε το πρότυπο για την εργασία Chocolatey στο παράθυρο εργασίες κατά την επεξεργασία ενός αρχείου διαμόρφωσης azure-pipelines.yml:

Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps

Κάντε κλικ στο Chocolatey και δείτε μια λίστα με πεδία:

Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps

Εδώ πρέπει να επιλέξουμε εγκαθιστώ στον αγωνιστικό χώρο με τις ομάδες. ΣΕ Όνομα αρχείου Nuspec αναφέρετε το όνομα του απαιτούμενου πακέτου – pvs-studio. Εάν δεν προσδιορίσετε την έκδοση, θα εγκατασταθεί η πιο πρόσφατη, η οποία μας ταιριάζει απόλυτα. Ας πατήσουμε το κουμπί προσθέτω και θα δούμε την εργασία που δημιουργήθηκε στο αρχείο διαμόρφωσης.

steps:
- task: ChocolateyCommand@0
  inputs:
    command: 'install'
    installPackageId: 'pvs-studio'

Στη συνέχεια, ας περάσουμε στο κύριο μέρος του αρχείου μας:

- task: CmdLine@2
  inputs:
    script: 

Τώρα πρέπει να δημιουργήσουμε ένα αρχείο με την άδεια του αναλυτή. Εδώ PVSNAME и PVSKEY – ονόματα μεταβλητών των οποίων τις τιμές καθορίζουμε στις ρυθμίσεις. Θα αποθηκεύσουν το κλειδί σύνδεσης και άδειας χρήσης του PVS-Studio. Για να ορίσετε τις τιμές τους, ανοίξτε το μενού Μεταβλητές->Νέα μεταβλητή. Ας δημιουργήσουμε μεταβλητές PVSNAME για είσοδο και PVSKEY για το κλειδί του αναλυτή. Μην ξεχάσετε να επιλέξετε το πλαίσιο Κρατήστε αυτή την τιμή μυστική για PVSKEY. Κωδικός εντολής:

сall "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe" credentials 
–u $(PVSNAME) –n $(PVSKEY)

Ας δημιουργήσουμε το έργο χρησιμοποιώντας το αρχείο bat που βρίσκεται στο αποθετήριο:

сall build.bat

Ας δημιουργήσουμε έναν φάκελο όπου θα αποθηκεύονται αρχεία με τα αποτελέσματα του αναλυτή:

сall mkdir PVSTestResults

Ας αρχίσουμε να αναλύουμε το έργο:

сall "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe" 
–t .srcchocolatey.sln –o .PVSTestResultsChoco.plog 

Μετατρέπουμε την αναφορά μας σε μορφή html χρησιμοποιώντας το βοηθητικό πρόγραμμα PlogСonverter:

сall "C:Program Files (x86)PVS-StudioPlogConverter.exe" 
–t html –o PVSTestResults .PVSTestResultsChoco.plog

Τώρα πρέπει να δημιουργήσετε μια εργασία για να μπορείτε να ανεβάσετε την αναφορά.

- task: PublishBuildArtifacts@1
  inputs:
    pathToPublish: PVSTestResults
    artifactName: PVSTestResults
    condition: always()

Το πλήρες αρχείο ρυθμίσεων μοιάζει με αυτό:

trigger:
- master

pool:
  vmImage: 'windows-latest'

steps:
- task: ChocolateyCommand@0
  inputs:
    command: 'install'
    installPackageId: 'pvs-studio'

- task: CmdLine@2
  inputs:
    script: |
      call "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe" 
      credentials –u $(PVSNAME) –n $(PVSKEY)
      call build.bat
      call mkdir PVSTestResults
      call "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe" 
      –t .srcchocolatey.sln –o .PVSTestResultsChoco.plog
      call "C:Program Files (x86)PVS-StudioPlogConverter.exe" 
      –t html –o .PVSTestResults .PVSTestResultsChoco.plog

- task: PublishBuildArtifacts@1
  inputs:
    pathToPublish: PVSTestResults
    artifactName: PVSTestResults
    condition: always()

Ας κάνουμε κλικ Αποθήκευση->Αποθήκευση->Εκτέλεση για να εκτελέσετε την εργασία. Ας κατεβάσουμε την αναφορά μεταβαίνοντας στην καρτέλα εργασιών.

Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps

Το έργο Chocolatey περιέχει μόνο 37615 γραμμές κώδικα C#. Ας δούμε μερικά από τα σφάλματα που εντοπίστηκαν.

Αποτελέσματα δοκιμών

Προειδοποίηση N1

Προειδοποίηση αναλυτή: V3005 Η μεταβλητή 'Provider' εκχωρείται στον εαυτό της. CrytpoHashProviderSpecs.cs 38

public abstract class CrytpoHashProviderSpecsBase : TinySpec
{
  ....
  protected CryptoHashProvider Provider;
  ....
  public override void Context()
  {
    Provider = Provider = new CryptoHashProvider(FileSystem.Object);
  }
}

Ο αναλυτής εντόπισε μια εκχώρηση της μεταβλητής στον εαυτό του, κάτι που δεν έχει νόημα. Πιθανότατα, στη θέση μιας από αυτές τις μεταβλητές θα πρέπει να υπάρχει κάποια άλλη. Λοιπόν, ή αυτό είναι τυπογραφικό λάθος, και η επιπλέον ανάθεση μπορεί απλά να αφαιρεθεί.

Προειδοποίηση N2

Προειδοποίηση αναλυτή: V3093 [CWE-480] Ο τελεστής '&' αξιολογεί και τους δύο τελεστές. Ίσως θα πρέπει να χρησιμοποιηθεί ένας χειριστής βραχυκυκλώματος "&&". Platform.cs 64

public static PlatformType get_platform()
{
  switch (Environment.OSVersion.Platform)
  {
    case PlatformID.MacOSX:
    {
      ....
    }
    case PlatformID.Unix:
    if(file_system.directory_exists("/Applications")
      & file_system.directory_exists("/System")
      & file_system.directory_exists("/Users")
      & file_system.directory_exists("/Volumes"))
      {
        return PlatformType.Mac;
      }
        else
          return PlatformType.Linux;
    default:
      return PlatformType.Windows;
  }
}

Διαφορά χειριστή & από τον χειριστή && είναι ότι αν η αριστερή πλευρά της έκφρασης είναι ψευδής, τότε θα εξακολουθήσει να υπολογίζεται η δεξιά πλευρά, πράγμα που σε αυτήν την περίπτωση συνεπάγεται περιττές κλήσεις μεθόδου system.directory_exists.

Στο εξεταζόμενο απόσπασμα, αυτό είναι ένα μικρό ελάττωμα. Ναι, αυτή η συνθήκη μπορεί να βελτιστοποιηθεί με την αντικατάσταση του τελεστή & με τον τελεστή &&, αλλά από πρακτική άποψη, αυτό δεν επηρεάζει τίποτα. Ωστόσο, σε άλλες περιπτώσεις, η σύγχυση μεταξύ & και && μπορεί να προκαλέσει σοβαρά προβλήματα όταν η δεξιά πλευρά της έκφρασης αντιμετωπίζεται με λανθασμένες/μη έγκυρες τιμές. Για παράδειγμα, στη συλλογή σφαλμάτων μας, αναγνωρίστηκε χρησιμοποιώντας το διαγνωστικό V3093, υπάρχει αυτή η περίπτωση:

if ((k < nct) & (s[k] != 0.0))

Ακόμα κι αν ο δείκτης k είναι λάθος, θα χρησιμοποιηθεί για πρόσβαση σε ένα στοιχείο πίνακα. Ως αποτέλεσμα, θα υπάρξει εξαίρεση IndexOutOfRangeException.

Προειδοποιήσεις N3, N4

Προειδοποίηση αναλυτή: V3022 [CWE-571] Η έκφραση "shortPrompt" είναι πάντα αληθής. InteractivePrompt.cs 101
Προειδοποίηση αναλυτή: V3022 [CWE-571] Η έκφραση "shortPrompt" είναι πάντα αληθής. InteractivePrompt.cs 105

public static string 
prompt_for_confirmation(.... bool shortPrompt = false, ....)
{
  ....
  if (shortPrompt)
  {
    var choicePrompt = choice.is_equal_to(defaultChoice) //1
    ?
    shortPrompt //2
    ?
    "[[{0}]{1}]".format_with(choice.Substring(0, 1).ToUpperInvariant(), //3
    choice.Substring(1,choice.Length - 1))
    :
    "[{0}]".format_with(choice.ToUpperInvariant()) //0
    : 
    shortPrompt //4
    ? 
    "[{0}]{1}".format_with(choice.Substring(0,1).ToUpperInvariant(), //5
    choice.Substring(1,choice.Length - 1)) 
    :
    choice; //0
    ....
  }
  ....
}

Σε αυτή την περίπτωση, υπάρχει μια περίεργη λογική πίσω από τη λειτουργία του τριαδικού τελεστή. Ας ρίξουμε μια πιο προσεκτική ματιά: εάν πληρούται η προϋπόθεση που σημείωσα με τον αριθμό 1, τότε θα προχωρήσουμε στην συνθήκη 2, η οποία είναι πάντα αληθής, που σημαίνει ότι θα εκτελεστεί η γραμμή 3. Εάν η συνθήκη 1 αποδειχθεί ψευδής, τότε θα πάμε στη γραμμή που σημειώνεται με τον αριθμό 4, η συνθήκη στην οποία είναι επίσης πάντα αληθής, που σημαίνει ότι θα εκτελεστεί η γραμμή 5. Έτσι, οι προϋποθέσεις που επισημαίνονται με το σχόλιο 0 δεν θα εκπληρωθούν ποτέ, κάτι που μπορεί να μην είναι ακριβώς η λογική λειτουργίας που περίμενε ο προγραμματιστής.

Προειδοποίηση N5

Προειδοποίηση αναλυτή: V3123 [CWE-783] Ίσως ο τελεστής '?:' να λειτουργεί με διαφορετικό τρόπο από ό,τι αναμενόταν. Η προτεραιότητά του είναι χαμηλότερη από την προτεραιότητα άλλων χειριστών στην κατάστασή του. Options.cs 1019

private static string GetArgumentName (...., string description)
{
  string[] nameStart;
  if (maxIndex == 1)
  {
    nameStart = new string[]{"{0:", "{"};
  }
  else
  {
    nameStart = new string[]{"{" + index + ":"};
  }
  for (int i = 0; i < nameStart.Length; ++i) 
  {
    int start, j = 0;
    do 
    {
      start = description.IndexOf (nameStart [i], j);
    } 
    while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
    ....
    return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
  }
}

Το διαγνωστικό λειτούργησε για τη γραμμή:

while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false)

Αφού η μεταβλητή j μερικές γραμμές παραπάνω αρχικοποιείται στο μηδέν, ο τριαδικός τελεστής θα επιστρέψει την τιμή ψευδής. Λόγω αυτής της συνθήκης, το σώμα του βρόχου θα εκτελεστεί μόνο μία φορά. Μου φαίνεται ότι αυτό το κομμάτι κώδικα δεν λειτουργεί καθόλου όπως ήθελε ο προγραμματιστής.

Προειδοποίηση N6

Προειδοποίηση αναλυτή: V3022 [CWE-571] Η έκφραση 'installedPackageVersions.Count != 1' είναι πάντα αληθής. NugetService.cs 1405

private void remove_nuget_cache_for_package(....)
{
  if (!config.AllVersions && installedPackageVersions.Count > 1)
  {
    const string allVersionsChoice = "All versions";
    if (installedPackageVersions.Count != 1)
    {
      choices.Add(allVersionsChoice);
    }
    ....
  }
  ....
}

Υπάρχει μια περίεργη ένθετη συνθήκη εδώ: installedPackageVersions.Count != 1που θα είναι πάντα αληθής. Συχνά μια τέτοια προειδοποίηση υποδεικνύει ένα λογικό σφάλμα στον κώδικα και σε άλλες περιπτώσεις απλώς υποδεικνύει περιττό έλεγχο.

Προειδοποίηση N7

Προειδοποίηση αναλυτή: V3001 Υπάρχουν πανομοιότυπες υπο-εκφράσεις 'commandArguments.contains("-apikey")' στα αριστερά και στα δεξιά του '||' χειριστής. ArgumentsUtility.cs 42

public static bool arguments_contain_sensitive_information(string
 commandArguments)
{
  return commandArguments.contains("-install-arguments-sensitive")
  || commandArguments.contains("-package-parameters-sensitive")
  || commandArguments.contains("apikey ")
  || commandArguments.contains("config ")
  || commandArguments.contains("push ")
  || commandArguments.contains("-p ")
  || commandArguments.contains("-p=")
  || commandArguments.contains("-password")
  || commandArguments.contains("-cp ")
  || commandArguments.contains("-cp=")
  || commandArguments.contains("-certpassword")
  || commandArguments.contains("-k ")
  || commandArguments.contains("-k=")
  || commandArguments.contains("-key ")
  || commandArguments.contains("-key=")
  || commandArguments.contains("-apikey")
  || commandArguments.contains("-api-key")
  || commandArguments.contains("-apikey")
  || commandArguments.contains("-api-key");
}

Ο προγραμματιστής που έγραψε αυτό το τμήμα κώδικα, αντέγραψε και επικόλλησε τις δύο τελευταίες γραμμές και ξέχασε να τις επεξεργαστεί. Εξαιτίας αυτού, οι χρήστες του Chocolatey δεν μπόρεσαν να εφαρμόσουν την παράμετρο apikey μερικούς ακόμα τρόπους. Παρόμοια με τις παραπάνω παραμέτρους, μπορώ να προσφέρω τις ακόλουθες επιλογές:

commandArguments.contains("-apikey=");
commandArguments.contains("-api-key=");

Τα σφάλματα αντιγραφής-επικόλλησης έχουν μεγάλες πιθανότητες να εμφανιστούν αργά ή γρήγορα σε οποιοδήποτε έργο με μεγάλη ποσότητα πηγαίου κώδικα και ένα από τα καλύτερα εργαλεία για την καταπολέμησή τους είναι η στατική ανάλυση.

ΥΓ Και όπως πάντα, αυτό το σφάλμα τείνει να εμφανίζεται στο τέλος μιας συνθήκης πολλών γραμμών :). Δείτε τη δημοσίευση "Εφέ τελευταίας γραμμής".

Προειδοποίηση N8

Προειδοποίηση αναλυτή: V3095 [CWE-476] Το αντικείμενο 'installedPackage' χρησιμοποιήθηκε πριν επαληθευτεί ως μηδενικό. Ελέγξτε τις γραμμές: 910, 917. NugetService.cs 910

public virtual ConcurrentDictionary<string, PackageResult> get_outdated(....)
{
  ....
  var pinnedPackageResult = outdatedPackages.GetOrAdd(
    packageName, 
    new PackageResult(installedPackage, 
                      _fileSystem.combine_paths(
                        ApplicationParameters.PackagesLocation, 
                        installedPackage.Id)));
  ....
  if (   installedPackage != null
      && !string.IsNullOrWhiteSpace(installedPackage.Version.SpecialVersion) 
      && !config.UpgradeCommand.ExcludePrerelease)
  {
    ....
  }
  ....
}

Κλασικό λάθος: αντικρούστε πρώτα εγκατεστημένο πακέτο χρησιμοποιείται και στη συνέχεια ελέγχεται για μηδέν. Αυτό το διαγνωστικό μας λέει για ένα από τα δύο προβλήματα του προγράμματος: είτε εγκατεστημένο πακέτο ποτέ ίσος μηδέν, το οποίο είναι αμφίβολο και τότε ο έλεγχος είναι περιττός, διαφορετικά θα μπορούσαμε να λάβουμε ένα σοβαρό σφάλμα στον κώδικα - μια προσπάθεια πρόσβασης σε μια μηδενική αναφορά.

Συμπέρασμα

Έτσι, κάναμε ένα άλλο μικρό βήμα - τώρα η χρήση του PVS-Studio έχει γίνει ακόμα πιο εύκολη και πιο βολική. Θα ήθελα επίσης να πω ότι το Chocolatey είναι ένας καλός διαχειριστής πακέτων με μικρό αριθμό σφαλμάτων στον κώδικα, τα οποία θα μπορούσαν να είναι ακόμη λιγότερα όταν χρησιμοποιείτε το PVS-Studio.

Σας προσκαλούμε κατεβάσετε και δοκιμάστε το PVS-Studio. Η τακτική χρήση ενός στατικού αναλυτή θα βελτιώσει την ποιότητα και την αξιοπιστία του κώδικα που αναπτύσσει η ομάδα σας και θα βοηθήσει στην αποτροπή πολλών τρωτά σημεία zero day.

PS

Πριν από τη δημοσίευση, στείλαμε το άρθρο στους προγραμματιστές του Chocolatey και το έλαβαν καλά. Δεν βρήκαμε τίποτα κρίσιμο, αλλά τους άρεσε, για παράδειγμα, το σφάλμα που βρήκαμε σχετικά με το κλειδί "api-key".

Το PVS-Studio βρίσκεται τώρα στο Chocolatey: ελέγχοντας το Chocolatey από το Azure DevOps

Εάν θέλετε να μοιραστείτε αυτό το άρθρο με ένα αγγλόφωνο κοινό, χρησιμοποιήστε τον σύνδεσμο μετάφρασης: Vladislav Stolyarov. Το PVS-Studio είναι τώρα στο Chocolatey: Έλεγχος Chocolatey στο Azure DevOps.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο