[PATCH] testbot: Add SpecialJobs to handle the reconfig / winetest jobs.

Francois Gouget fgouget at codeweavers.com
Wed Sep 25 01:52:48 CDT 2019


This allows creating these jobs without having to go through
CheckForWinetestUpdate.pl.

Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
---
 testbot/bin/CheckForWinetestUpdate.pl  | 284 ++++---------------------
 testbot/lib/WineTestBot/SpecialJobs.pm | 276 ++++++++++++++++++++++++
 2 files changed, 323 insertions(+), 237 deletions(-)
 create mode 100644 testbot/lib/WineTestBot/SpecialJobs.pm

diff --git a/testbot/bin/CheckForWinetestUpdate.pl b/testbot/bin/CheckForWinetestUpdate.pl
index c30da162a..3f697315b 100755
--- a/testbot/bin/CheckForWinetestUpdate.pl
+++ b/testbot/bin/CheckForWinetestUpdate.pl
@@ -51,12 +51,8 @@ use HTTP::Status;
 
 use WineTestBot::Config;
 use WineTestBot::Engine::Notify;
-use WineTestBot::Jobs;
 use WineTestBot::Log;
-use WineTestBot::Missions;
-use WineTestBot::PatchUtils;
-use WineTestBot::Users;
-use WineTestBot::Utils;
+use WineTestBot::SpecialJobs;
 use WineTestBot::VMs;
 
 
@@ -168,267 +164,81 @@ sub UpdateWineTest($$)
   return (1, $LatestBaseName);
 }
 
-sub AddJob($$$$)
+sub DoReconfig($$)
 {
-  my ($VMKey, $BaseJob, $LatestBaseName, $Build) = @_;
-
-  my $Remarks = defined $VMKey ? "$VMKey VM" :
-                $Build eq "exe64" ? "64 bit VMs" :
-                "$BaseJob VMs";
-  $Remarks = "WineTest: $Remarks";
-  Debug("Creating the '$Remarks' job\n");
-
-  my $VMs = CreateVMs();
-  $VMs->AddFilter("Name", [$VMKey]) if (defined $VMKey);
-  if ($Build eq "exe64")
-  {
-    $VMs->AddFilter("Type", ["win64"]);
-    $VMs->AddFilter("Role", ["base", "winetest"]) if (!defined $VMKey);
-  }
-  elsif ($BaseJob eq "base")
-  {
-    $VMs->AddFilter("Type", ["win32", "win64"]);
-    $VMs->AddFilter("Role", ["base"]) if (!defined $VMKey);
-  }
-  else
-  {
-    $VMs->AddFilter("Type", ["win32", "win64"]);
-    $VMs->AddFilter("Role", ["winetest"]) if (!defined $VMKey);
-  }
-  if ($VMs->GetItemsCount() == 0)
-  {
-    if (defined $VMKey)
-    {
-      Error "The $VMKey VM is not suitable for $Build WineTest jobs\n";
-      return 0;
-    }
-    # There is nothing to do
-    Debug("  Found no VM\n");
-    return 1;
-  }
-
-  # First create a new job
-  my $Jobs = CreateJobs();
-  my $NewJob = $Jobs->Add();
-  $NewJob->User(GetBatchUser());
-  $NewJob->Priority(($BaseJob eq "base" and $Build eq "exe32") ? 8 : 9);
-  $NewJob->Remarks($Remarks);
-
-  # Add a task for each VM
-  my $Tasks;
-  foreach my $VMKey (@{$VMs->SortKeysBySortOrder($VMs->GetKeys())})
+  my ($VMKey, $VMType) = @_;
+  Debug("Creating the $VMType reconfig job\n");
+  my $VMs = GetReconfigVMs($VMKey, $VMType);
+  if (@$VMs)
   {
-    my $VM = $VMs->GetItem($VMKey);
-    my ($ErrMessage, $Missions) = ParseMissionStatement($VM->Missions);
+    my $ErrMessage = AddReconfigJob($VMs, $VMKey, $VMType);
     if (defined $ErrMessage)
     {
-      Debug("$VMKey has an invalid mission statement: $!\n");
-      next;
-    }
-
-    foreach my $TaskMissions (@$Missions)
-    {
-      next if (!$TaskMissions->{Builds}->{$Build});
-
-      if (!$Tasks)
-      {
-        # Add a step to the job
-        my $TestStep = $NewJob->Steps->Add();
-        $TestStep->Type("suite");
-        $TestStep->FileName($LatestBaseName);
-        $TestStep->FileType($Build);
-        $Tasks = $TestStep->Tasks;
-      }
-
-      Debug("  $VMKey $Build\n");
-      my $Task = $Tasks->Add();
-      $Task->VM($VM);
-      $Task->Timeout($SuiteTimeout);
-      $Task->Missions($TaskMissions->{Statement});
+      ErrorMessage("$ErrMessage\n");
+      return 0;
     }
   }
-
-  # Save it all
-  my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
-  if (defined $ErrMessage)
+  elsif (defined $VMKey)
   {
-    LogMsg "Failed to save the $LatestBaseName job: $ErrMessage\n";
+    Error("The $VMKey VM is not a $VMType VM\n");
     return 0;
   }
-
-  # Stage the test file so it can be picked up by the job
-  if (!link("$DataDir/latest/$LatestBaseName",
-            "$DataDir/staging/job". $NewJob->Id ."_$LatestBaseName"))
-  {
-    Error "Failed to stage $LatestBaseName: $!\n";
-    return 0;
-  }
-
-  # Switch Status to staging to indicate we are done setting up the job
-  $NewJob->Status("staging");
-  ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
-  if (defined $ErrMessage)
+  else
   {
-    Error "Failed to save the $LatestBaseName job (staging): $ErrMessage\n";
-    return 0;
+    Debug("Found no VM for the $VMType reconfig job\n");
   }
-
   return 1;
 }
 
-sub AddWineTestJob($)
+sub DoWindowsTest($$$$)
 {
-  my ($VMKey) = @_;
-
-  my $Remarks = defined $VMKey ? "$VMKey VM" : "Wine VMs";
-  $Remarks = "WineTest: $Remarks";
-  Debug("Creating the '$Remarks' job\n");
-
-  my $VMs = CreateVMs();
-  $VMs->AddFilter("Name", [$VMKey]) if (defined $VMKey);
-  $VMs->AddFilter("Type", ["wine"]);
-  $VMs->FilterEnabledRole();
-  if ($VMs->GetItemsCount() == 0)
+  my ($VMKey, $Build, $BaseJob, $LatestBaseName) = @_;
+  Debug("Creating the $BaseJob $Build Windows tests job\n");
+  my $VMs = GetWindowsTestVMs($VMKey, $Build, $BaseJob);
+  if (@$VMs)
   {
-    if (defined $VMKey)
-    {
-      Error "The $VMKey VM is not a wine VM\n";
-      return 0;
-    }
-    # There is nothing to do
-    Debug("  Found no VM\n");
-    return 1;
-  }
-
-  # First create a new job
-  my $Jobs = CreateJobs();
-  my $NewJob = $Jobs->Add();
-  $NewJob->User(GetBatchUser());
-  $NewJob->Priority(7);
-  $NewJob->Remarks($Remarks);
-  my $Steps = $NewJob->Steps;
-
-  # Add a step for each VM
-  my $SortedKeys = $VMs->SortKeysBySortOrder($VMs->GetKeys());
-  foreach my $VMKey (@$SortedKeys)
-  {
-    my $VM = $VMs->GetItem($VMKey);
-    # Move all the missions into separate tasks so we don't have one very
-    # long task hogging the VM forever. Note that this is also ok because
-    # the WineTest tasks don't have to recompile Wine.
-    my $MissionStatement = SplitMissionStatementTasks($VM->Missions);
-    my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement);
+    my $ErrMessage = AddWindowsTestJob($VMs, $VMKey, $Build, $BaseJob, $LatestBaseName);
     if (defined $ErrMessage)
     {
-      Debug("$VMKey has an invalid mission statement: $!\n");
-      next;
-    }
-
-    my $Tasks;
-    foreach my $TaskMissions (@$Missions)
-    {
-      if (!$Tasks)
-      {
-        # Add a step to the job
-        my $TestStep = $Steps->Add();
-        $TestStep->Type("suite");
-        $TestStep->FileType("none");
-        $Tasks = $TestStep->Tasks;
-      }
-
-      Debug("  $VMKey $TaskMissions->{Statement}\n");
-      my $Task = $Tasks->Add();
-      $Task->VM($VM);
-      $Task->Timeout(GetTestTimeout(undef, $TaskMissions));
-      $Task->Missions($TaskMissions->{Statement});
+      ErrorMessage("$ErrMessage\n");
+      return 0;
     }
   }
-
-  # Save it all
-  $NewJob->Status("staging");
-  my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
-  if (defined $ErrMessage)
+  elsif (defined $VMKey)
   {
-    Error "Failed to save the WineTest job: $ErrMessage\n";
+    Error("The $VMKey VM is not suitable for $BaseJob $Build Windows tests jobs\n");
     return 0;
   }
-
+  else
+  {
+    Debug("Found no VM for the $BaseJob $Build Windows tests job\n");
+  }
   return 1;
 }
 
-sub AddReconfigJob($$)
+sub DoWineTest($)
 {
-  my ($VMKey, $VMType) = @_;
-
-  my $Remarks = defined $VMKey ? "$VMKey $VMType VM" : "$VMType VMs";
-  $Remarks = "Update the $Remarks";
-  Debug("Creating the '$Remarks' job\n");
-
-  my $VMs = CreateVMs();
-  $VMs->AddFilter("Name", [$VMKey]) if (defined $VMKey);
-  $VMs->AddFilter("Type", [$VMType]);
-  $VMs->FilterEnabledRole();
-  if ($VMs->GetItemsCount() == 0)
+  my ($VMKey) = @_;
+  Debug("Creating the Wine tests job\n");
+  my $VMs = GetWineTestVMs($VMKey);
+  if (@$VMs)
   {
-    if (defined $VMKey)
-    {
-      Error "The $VMKey VM is not a $VMType VM\n";
-      return 0;
-    }
-    # There is nothing to do
-    Debug("  Found no VM\n");
-    return 1;
-  }
-
-  # First create a new job
-  my $Jobs = CreateJobs();
-  my $NewJob = $Jobs->Add();
-  $NewJob->User(GetBatchUser());
-  $NewJob->Priority(3);
-  $NewJob->Remarks($Remarks);
-
-  # Add a step to the job
-  my $Steps = $NewJob->Steps;
-  my $BuildStep = $Steps->Add();
-  $BuildStep->Type("reconfig");
-  $BuildStep->FileType("none");
-
-  # Add a task for each VM
-  my $SortedKeys = $VMs->SortKeysBySortOrder($VMs->GetKeys());
-  foreach my $VMKey (@$SortedKeys)
-  {
-    my $VM = $VMs->GetItem($VMKey);
-    Debug("  $VMKey $VMType reconfig\n");
-    my $Task = $BuildStep->Tasks->Add();
-    $Task->VM($VM);
-
-    # Merge all the tasks into one so we only recreate the base snapshot once
-    my $MissionStatement = $VMType ne "wine" ? "exe32:exe64" :
-                           MergeMissionStatementTasks($VM->Missions);
-    my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement);
+    my $ErrMessage = AddWineTestJob($VMs, $VMKey);
     if (defined $ErrMessage)
     {
-      Debug("$VMKey has an invalid mission statement: $!\n");
-      next;
-    }
-    if (@$Missions != 1)
-    {
-      Debug("Found no mission or too many task missions for $VMKey\n");
-      next;
+      ErrorMessage("$ErrMessage\n");
+      return 0;
     }
-    $Task->Timeout(GetBuildTimeout(undef, $Missions->[0]));
-    $Task->Missions($Missions->[0]->{Statement});
   }
-
-  # Save it all
-  $NewJob->Status("staging");
-  my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
-  if (defined $ErrMessage)
+  elsif (defined $VMKey)
   {
-    Error "Failed to save the Reconfig job: $ErrMessage\n";
+    Error("The $VMKey VM is not suitable for Wine tests jobs\n");
     return 0;
   }
-
+  else
+  {
+    Debug("Found no VM for the Wine tests job\n");
+  }
   return 1;
 }
 
@@ -566,12 +376,12 @@ if ($OptTypes{build} or $OptTypes{base32} or $OptTypes{other32} or
     # A new executable means there have been commits so update Wine. Create
     # this job first purely to make the WineTestBot job queue look nice, and
     # arbitrarily do it only for 32-bit executables to avoid redundant updates.
-    $Rc = 1 if ($OptTypes{build} and !AddReconfigJob($OptVMKey, "build"));
-    $Rc = 1 if ($OptTypes{base32} and !AddJob($OptVMKey, "base", $LatestBaseName, "exe32"));
-    $Rc = 1 if ($OptTypes{other32} and !AddJob($OptVMKey, "other", $LatestBaseName, "exe32"));
+    $Rc = 1 if ($OptTypes{build} and !DoReconfig($OptVMKey, "build"));
+    $Rc = 1 if ($OptTypes{base32} and !DoWindowsTest($OptVMKey, "exe32", "base", $LatestBaseName));
+    $Rc = 1 if ($OptTypes{other32} and !DoWindowsTest($OptVMKey, "exe32", "other", $LatestBaseName));
 
-    $Rc = 1 if ($OptTypes{winebuild} and !AddReconfigJob($OptVMKey, "wine"));
-    $Rc = 1 if ($OptTypes{winetest} and !AddWineTestJob($OptVMKey));
+    $Rc = 1 if ($OptTypes{winebuild} and !DoReconfig($OptVMKey, "wine"));
+    $Rc = 1 if ($OptTypes{winetest} and !DoWineTest($OptVMKey));
   }
 }
 
@@ -589,7 +399,7 @@ if ($OptTypes{all64})
   }
   elsif ($Create == 1)
   {
-    $Rc = 1 if ($OptTypes{all64} and !AddJob($OptVMKey, "", $LatestBaseName, "exe64"));
+    $Rc = 1 if ($OptTypes{all64} and !DoWindowsTest($OptVMKey, "exe64", "", $LatestBaseName));
   }
 }
 
diff --git a/testbot/lib/WineTestBot/SpecialJobs.pm b/testbot/lib/WineTestBot/SpecialJobs.pm
new file mode 100644
index 000000000..b0cb1480d
--- /dev/null
+++ b/testbot/lib/WineTestBot/SpecialJobs.pm
@@ -0,0 +1,276 @@
+# -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*-
+#
+# Provides methods to create the jobs that update Wine or run the full
+# WineTest suite.
+#
+# Copyright 2009 Ge van Geldorp
+# Copyright 2018-2019 Francois Gouget
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+use strict;
+
+package WineTestBot::SpecialJobs;
+
+=head1 NAME
+
+WineTestBot::SpecialJobs - Create the jobs that update Wine or run the full
+WineTest suite.
+
+=cut
+
+use Exporter 'import';
+our @EXPORT = qw(GetReconfigVMs AddReconfigJob
+                 GetWindowsTestVMs AddWindowsTestJob
+                 GetWineTestVMs AddWineTestJob);
+
+use WineTestBot::Config;
+use WineTestBot::Jobs;
+use WineTestBot::Missions;
+use WineTestBot::PatchUtils; # Get*Timeout()
+use WineTestBot::Users;
+use WineTestBot::VMs;
+
+
+sub GetReconfigVMs($$)
+{
+  my ($VMKey, $VMType) = @_;
+
+  my $VMs = CreateVMs();
+  $VMs->AddFilter("Name", [$VMKey]) if (defined $VMKey);
+  $VMs->AddFilter("Type", [$VMType]);
+  $VMs->FilterEnabledRole();
+
+  my $SortedKeys = $VMs->SortKeysBySortOrder($VMs->GetKeys());
+  my @SortedVMs = map { $VMs->GetItem($_) } @$SortedKeys;
+  return \@SortedVMs;
+}
+
+sub AddReconfigJob($$$)
+{
+  my ($VMs, $VMKey, $VMType) = @_;
+
+  # First create a new job
+  my $Jobs = CreateJobs();
+  my $NewJob = $Jobs->Add();
+  $NewJob->User(GetBatchUser());
+  $NewJob->Priority(3);
+  my $Remarks = defined $VMKey ? "$VMKey $VMType VM" : "$VMType VMs";
+  $NewJob->Remarks("Update the $Remarks");
+
+  # Add a step to the job
+  my $Steps = $NewJob->Steps;
+  my $BuildStep = $Steps->Add();
+  $BuildStep->Type("reconfig");
+  $BuildStep->FileType("none");
+
+  # Add a task for each VM
+  foreach my $VM (@$VMs)
+  {
+    my $Task = $BuildStep->Tasks->Add();
+    $Task->VM($VM);
+
+    # Merge all the tasks into one so we only recreate the base snapshot once
+    my $MissionStatement = $VM->Type ne "wine" ? "exe32:exe64" :
+                           MergeMissionStatementTasks($VM->Missions);
+    my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement);
+    if (defined $ErrMessage)
+    {
+      return "$VMKey has an invalid mission statement: $ErrMessage";
+    }
+    if (@$Missions != 1)
+    {
+      return "Found no mission or too many task missions for $VMKey";
+    }
+    $Task->Timeout(GetBuildTimeout(undef, $Missions->[0]));
+    $Task->Missions($Missions->[0]->{Statement});
+  }
+
+  # Save it all
+  $NewJob->Status("staging");
+  my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
+  if (defined $ErrMessage)
+  {
+    return "Failed to save the '$Remarks' job: $ErrMessage";
+  }
+  return undef;
+}
+
+sub GetWindowsTestVMs($$$)
+{
+  my ($VMKey, $Build, $BaseJob) = @_;
+
+  my $VMs = CreateVMs();
+  $VMs->AddFilter("Name", [$VMKey]) if (defined $VMKey);
+  if ($Build eq "exe64")
+  {
+    $VMs->AddFilter("Type", ["win64"]);
+    $VMs->AddFilter("Role", ["base", "winetest"]) if (!defined $VMKey);
+  }
+  elsif ($BaseJob eq "base")
+  {
+    $VMs->AddFilter("Type", ["win32", "win64"]);
+    $VMs->AddFilter("Role", ["base"]) if (!defined $VMKey);
+  }
+  else
+  {
+    $VMs->AddFilter("Type", ["win32", "win64"]);
+    $VMs->AddFilter("Role", ["winetest"]) if (!defined $VMKey);
+  }
+
+  my $SortedKeys = $VMs->SortKeysBySortOrder($VMs->GetKeys());
+  my @SortedVMs = map { $VMs->GetItem($_) } @$SortedKeys;
+  return \@SortedVMs;
+}
+
+sub AddWindowsTestJob($$$$$)
+{
+  my ($VMs, $VMKey, $Build, $BaseJob, $LatestBaseName) = @_;
+
+  # First create a new job
+  my $Jobs = CreateJobs();
+  my $NewJob = $Jobs->Add();
+  $NewJob->User(GetBatchUser());
+  $NewJob->Priority(($BaseJob eq "base" and $Build eq "exe32") ? 8 : 9);
+  my $Remarks = defined $VMKey ? "$VMKey VM" :
+                $Build eq "exe64" ? "64 bit VMs" :
+                "$BaseJob VMs";
+  $NewJob->Remarks("WineTest: $Remarks");
+
+  # Add a task for each VM
+  my $Tasks;
+  foreach my $VM (@$VMs)
+  {
+    my ($ErrMessage, $Missions) = ParseMissionStatement($VM->Missions);
+    if (defined $ErrMessage)
+    {
+      return "$VMKey has an invalid mission statement: $!";
+    }
+
+    foreach my $TaskMissions (@$Missions)
+    {
+      next if (!$TaskMissions->{Builds}->{$Build});
+
+      if (!$Tasks)
+      {
+        # Add a step to the job
+        my $TestStep = $NewJob->Steps->Add();
+        $TestStep->Type("suite");
+        $TestStep->FileName($LatestBaseName);
+        $TestStep->FileType($Build);
+        $Tasks = $TestStep->Tasks;
+      }
+
+      my $Task = $Tasks->Add();
+      $Task->VM($VM);
+      $Task->Timeout($SuiteTimeout);
+      $Task->Missions($TaskMissions->{Statement});
+    }
+  }
+
+  # Save it all
+  my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
+  if (defined $ErrMessage)
+  {
+    return "Failed to save the '$Remarks' job: $ErrMessage";
+  }
+
+  # Stage the test file so it can be picked up by the job
+  if (!link("$DataDir/latest/$LatestBaseName",
+            "$DataDir/staging/job". $NewJob->Id ."_$LatestBaseName"))
+  {
+    return "Failed to stage $LatestBaseName: $!";
+  }
+
+  # Switch Status to staging to indicate we are done setting up the job
+  $NewJob->Status("staging");
+  ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
+  if (defined $ErrMessage)
+  {
+    return "Failed to save the '$Remarks' job (staging): $ErrMessage";
+  }
+  return undef;
+}
+
+sub GetWineTestVMs($)
+{
+  my ($VMKey) = @_;
+
+  my $VMs = CreateVMs();
+  $VMs->AddFilter("Name", [$VMKey]) if (defined $VMKey);
+  $VMs->AddFilter("Type", ["wine"]);
+  $VMs->FilterEnabledRole();
+
+  my $SortedKeys = $VMs->SortKeysBySortOrder($VMs->GetKeys());
+  my @SortedVMs = map { $VMs->GetItem($_) } @$SortedKeys;
+  return \@SortedVMs;
+}
+
+sub AddWineTestJob($$)
+{
+  my ($VMs, $VMKey) = @_;
+
+  # First create a new job
+  my $Jobs = CreateJobs();
+  my $NewJob = $Jobs->Add();
+  $NewJob->User(GetBatchUser());
+  $NewJob->Priority(7);
+  my $Remarks = defined $VMKey ? "$VMKey VM" : "Wine VMs";
+  $NewJob->Remarks("WineTest: $Remarks");
+
+  # Add a step for each VM
+  foreach my $VM (@$VMs)
+  {
+    # Move all the missions into separate tasks so we don't have one very
+    # long task hogging the VM forever. Note that this is also ok because
+    # the WineTest tasks don't have to recompile Wine.
+    my $MissionStatement = SplitMissionStatementTasks($VM->Missions);
+    my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement);
+    if (defined $ErrMessage)
+    {
+      return "$VMKey has an invalid mission statement: $!";
+    }
+
+    my $Tasks;
+    foreach my $TaskMissions (@$Missions)
+    {
+      if (!$Tasks)
+      {
+        # Add a step to the job
+        my $TestStep = $NewJob->Steps->Add();
+        $TestStep->Type("suite");
+        $TestStep->FileType("none");
+        $Tasks = $TestStep->Tasks;
+      }
+
+      my $Task = $Tasks->Add();
+      $Task->VM($VM);
+      $Task->Timeout(GetTestTimeout(undef, $TaskMissions));
+      $Task->Missions($TaskMissions->{Statement});
+    }
+  }
+
+  # Save it all
+  $NewJob->Status("staging");
+  my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
+  if (defined $ErrMessage)
+  {
+    return "Failed to save the '$Remarks' job: $ErrMessage";
+  }
+
+  return undef;
+}
+
+1;
-- 
2.20.1




More information about the wine-devel mailing list