پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
PVS-Studio تجزیہ کار کے استعمال کے لیے موجودہ حالات میں سے ایک CI سسٹمز کے ساتھ اس کا انضمام ہے۔ اور اگرچہ تقریباً کسی بھی مسلسل انضمام کے نظام سے PVS-Studio پروجیکٹ کا تجزیہ صرف چند کمانڈز میں بنایا جا سکتا ہے، ہم اس عمل کو مزید آسان بناتے رہتے ہیں۔ PVS-Studio کو اب ٹیم سٹی - ٹیم سٹی معائنہ کی قسم کے لیے تجزیہ کار آؤٹ پٹ کو فارمیٹ میں تبدیل کرنے کے لیے تعاون حاصل ہے۔ آئیے دیکھتے ہیں کہ یہ کیسے کام کرتا ہے۔

استعمال شدہ سافٹ ویئر کے بارے میں معلومات

PVS- اسٹوڈیو — C, C++, C# اور Java کوڈ کا ایک جامد تجزیہ کار، مختلف قسم کی غلطیوں کو تلاش کرنے اور درست کرنے کے کام کو آسان بنانے کے لیے ڈیزائن کیا گیا ہے۔ تجزیہ کار ونڈوز، لینکس اور میک او ایس پر استعمال کیا جا سکتا ہے۔ اس مضمون میں ہم نہ صرف خود تجزیہ کار بلکہ اس کی تقسیم سے کچھ افادیت کو بھی فعال طور پر استعمال کریں گے۔

سی ایل مانیٹر - ایک مانیٹرنگ سرور ہے جو کمپائلر لانچوں کی نگرانی کرتا ہے۔ اپنے پروجیکٹ کی تعمیر شروع کرنے سے پہلے اسے فوری طور پر چلایا جانا چاہیے۔ اسنوپنگ موڈ میں، سرور تمام معاون کمپائلرز کے رنز کو روک دے گا۔ یہ بات قابل غور ہے کہ اس یوٹیلیٹی کو صرف C/C++ پروجیکٹس کا تجزیہ کرنے کے لیے استعمال کیا جا سکتا ہے۔

پلگ کنورٹر - تجزیہ کار رپورٹس کو مختلف فارمیٹس میں تبدیل کرنے کی افادیت۔

زیر مطالعہ منصوبے کے بارے میں معلومات

آئیے ایک عملی مثال پر اس فعالیت کو آزماتے ہیں - آئیے OpenRCT2 پروجیکٹ کا تجزیہ کرتے ہیں۔

اوپن آر سی ٹی 2 - گیم رولر کوسٹر ٹائکون 2 (RCT2) کا کھلا نفاذ، اسے نئے فنکشنز کے ساتھ بڑھانا اور کیڑے ٹھیک کرنا۔ گیم پلے تفریحی پارک کی تعمیر اور دیکھ بھال کے گرد گھومتا ہے جس میں سواری، دکانیں اور سہولیات شامل ہیں۔ کھلاڑی کو مہمانوں کو خوش رکھتے ہوئے منافع کمانے اور پارک کی اچھی ساکھ کو برقرار رکھنے کی کوشش کرنی چاہیے۔ OpenRCT2 آپ کو منظر نامے اور سینڈ باکس دونوں میں کھیلنے کی اجازت دیتا ہے۔ منظرناموں میں کھلاڑی کو ایک مقررہ وقت کے اندر ایک مخصوص کام مکمل کرنے کی ضرورت ہوتی ہے، جبکہ سینڈ باکس کھلاڑی کو بغیر کسی پابندی یا مالی اعانت کے زیادہ لچکدار پارک بنانے کی اجازت دیتا ہے۔

ایڈجسٹمنٹ

وقت بچانے کے لیے، میں شاید انسٹالیشن کے عمل کو چھوڑ دوں گا اور اس لمحے سے شروع کروں گا جب میرے کمپیوٹر پر TeamCity سرور چل رہا ہے۔ ہمیں اس پر جانے کی ضرورت ہے: لوکل ہوسٹ:{انسٹالیشن کے عمل کے دوران مخصوص کردہ پورٹ} (میرے معاملے میں، لوکل ہوسٹ:9090) اور اجازت کا ڈیٹا درج کریں۔ داخل ہونے کے بعد ہمارا استقبال کیا جائے گا:

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
پروجیکٹ بنائیں بٹن پر کلک کریں۔ اگلا، دستی طور پر منتخب کریں اور کھیتوں کو پُر کریں۔

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
بٹن دبانے کے بعد تخلیق کریں، ترتیبات کے ساتھ ایک ونڈو کے ذریعہ ہمارا استقبال کیا جاتا ہے۔

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
آئیے کلک کریں۔ تعمیر کنفیگریشن بنائیں.

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
کھیتوں کو بھریں اور کلک کریں۔ تخلیق کریں. ہم ایک ونڈو دیکھتے ہیں جو آپ سے ورژن کنٹرول سسٹم کو منتخب کرنے کے لیے کہتی ہے۔ چونکہ ذرائع پہلے ہی مقامی طور پر موجود ہیں، کلک کریں۔ جائیے.

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
آخر میں، ہم پروجیکٹ کی ترتیبات کی طرف بڑھتے ہیں۔

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
آئیے اسمبلی کے مراحل شامل کریں، ایسا کرنے کے لیے کلک کریں: تعمیراتی مراحل -> تعمیراتی قدم شامل کریں۔.

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
یہاں ہم منتخب کرتے ہیں:

  • رنر کی قسم -> کمانڈ لائن
  • چلائیں -> کسٹم اسکرپٹ

چونکہ ہم پروجیکٹ کی تالیف کے دوران تجزیہ کریں گے، اس لیے اسمبلی اور تجزیہ ایک قدم ہونا چاہیے، اس لیے فیلڈ کو پُر کریں۔ اپنی مرضی کے اسکرپٹ:

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
ہم انفرادی اقدامات کو بعد میں دیکھیں گے۔ یہ ضروری ہے کہ تجزیہ کار کو لوڈ کرنے، پراجیکٹ کو جمع کرنے، اس کا تجزیہ کرنے، رپورٹ کو آؤٹ پٹ کرنے اور اسے فارمیٹ کرنے کے لیے کوڈ کی صرف گیارہ لائنیں لگتی ہیں۔

آخری چیز جو ہمیں کرنے کی ضرورت ہے وہ ہے ماحولیات کے متغیرات کو ترتیب دینا، جس کی میں نے ان کی پڑھنے کی اہلیت کو بہتر بنانے کے کچھ طریقے بتائے ہیں۔ ایسا کرنے کے لیے، آئیے آگے بڑھتے ہیں: پیرامیٹرز -> نیا پیرامیٹر شامل کریں۔ اور تین متغیرات شامل کریں:

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
آپ کو بس بٹن دبانا ہے۔ رن اوپری دائیں کونے میں۔ جب پروجیکٹ کو جمع اور تجزیہ کیا جا رہا ہے، میں آپ کو اسکرپٹ کے بارے میں بتاؤں گا۔

براہ راست اسکرپٹ

سب سے پہلے، ہمیں تازہ ترین PVS-Studio ڈسٹری بیوشن ڈاؤن لوڈ کرنے کی ضرورت ہے۔ اس کے لیے ہم چاکلیٹی پیکیج مینیجر استعمال کرتے ہیں۔ ان لوگوں کے لیے جو اس کے بارے میں مزید جاننا چاہتے ہیں، اس سے متعلق ہے۔ مضمون:

choco install pvs-studio -y

اگلا، آئیے CLMonitor پروجیکٹ بلڈ ٹریکنگ یوٹیلیٹی لانچ کریں۔

%CLmon% monitor –-attach

پھر ہم اس منصوبے کو ماحولیاتی متغیر کے طور پر بنائیں گے۔ MSB MSBuild کے ورژن کا راستہ ہے جسے مجھے بنانے کی ضرورت ہے۔

%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable

آئیے PVS-Studio کے لیے لاگ ان اور لائسنس کلید درج کریں:

%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%

تعمیر مکمل ہونے کے بعد، پہلے سے پروسیس شدہ فائلیں اور جامد تجزیہ بنانے کے لیے دوبارہ CLMonitor چلائیں:

%CLmon% analyze -l "c:ptest.plog"

پھر ہم اپنی تقسیم سے ایک اور افادیت استعمال کریں گے۔ PlogConverter ایک رپورٹ کو معیاری فارمیٹ سے TeamCity کے مخصوص فارمیٹ میں تبدیل کرتا ہے۔ اس کا شکریہ، ہم اسے براہ راست بلڈ ونڈو میں دیکھ سکیں گے۔

%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"

آخری مرحلہ فارمیٹ شدہ رپورٹ کو ظاہر کرنا ہے۔ stdout، جہاں اسے TeamCity پارسر کے ذریعہ اٹھایا جائے گا۔

type "C:tempptest.plog_TeamCity.txt"

مکمل اسکرپٹ کوڈ:

choco install pvs-studio -y
%CLmon% monitor --attach
set platform=x64
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
%CLmon% analyze -l "c:ptest.plog"
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"
type "C:tempptest.plog_TeamCity.txt"

اس دوران، پروجیکٹ کی اسمبلی اور تجزیہ کامیابی سے مکمل ہو گیا ہے، ہم ٹیب پر جا سکتے ہیں منصوبوں کی تفصیل اور убедиться в этом.

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
اب آئیے پر کلک کریں۔ معائنہ کلتجزیہ کار کی رپورٹ دیکھنے کے لیے:

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
انتباہات کو تشخیصی اصول نمبروں کے مطابق گروپ کیا گیا ہے۔ کوڈ کے ذریعے نیویگیٹ کرنے کے لیے، آپ کو وارننگ کے ساتھ لائن نمبر پر کلک کرنے کی ضرورت ہے۔ اوپری دائیں کونے میں سوالیہ نشان پر کلک کرنے سے آپ کو دستاویزات کے ساتھ ایک نیا ٹیب کھل جائے گا۔ آپ تجزیہ کار کی وارننگ کے ساتھ لائن نمبر پر کلک کر کے کوڈ کے ذریعے بھی جا سکتے ہیں۔ استعمال کرتے وقت ریموٹ کمپیوٹر سے نیویگیشن ممکن ہے۔ SourceTreeRoot مارکر جو کوئی بھی تجزیہ کار کے آپریشن کے اس موڈ میں دلچسپی رکھتا ہے وہ متعلقہ سیکشن سے خود کو واقف کر سکتا ہے دستاویزات.

تجزیہ کار کے نتائج دیکھنا

اب جب کہ ہم نے تعمیر کی تعیناتی اور ترتیب مکمل کر لی ہے، آئیے اس پروجیکٹ میں پائے جانے والے کچھ دلچسپ انتباہات پر ایک نظر ڈالتے ہیں جو ہم دیکھ رہے ہیں۔

وارننگ N1

V773 [CWE-401] رعایت 'نتیجہ' پوائنٹر کو جاری کیے بغیر پھینک دی گئی۔ میموری لیک ہونے کا امکان ہے۔ libopenrct2 ObjectFactory.cpp 443

Object* CreateObjectFromJson(....)
{
  Object* result = nullptr;
  ....
  result = CreateObject(entry);
  ....
  if (readContext.WasError())
  {
    throw std::runtime_error("Object has errors");
  }
  ....
}

Object* CreateObject(const rct_object_entry& entry)
{
  Object* result;
  switch (entry.GetType())
  {
    case OBJECT_TYPE_RIDE:
      result = new RideObject(entry);
      break;
    case OBJECT_TYPE_SMALL_SCENERY:
      result = new SmallSceneryObject(entry);
      break;
    case OBJECT_TYPE_LARGE_SCENERY:
      result = new LargeSceneryObject(entry);
      break;
    ....
    default:
      throw std::runtime_error("Invalid object type");
  }
  return result;
}

تجزیہ کار نے ایک غلطی دیکھی جو متحرک طور پر میموری کو مختص کرنے کے بعد تخلیق آبجیکٹ، جب کوئی استثناء واقع ہوتا ہے، میموری کو صاف نہیں کیا جاتا ہے، اور میموری کا اخراج ہوتا ہے۔

وارننگ N2

V501 '(1ULL << WIDX_MONTH_BOX)' بائیں اور '|' کے دائیں طرف ایک جیسے ذیلی تاثرات ہیں۔ آپریٹر libopenrct2ui Cheats.cpp 487

static uint64_t window_cheats_page_enabled_widgets[] = 
{
  MAIN_CHEAT_ENABLED_WIDGETS |
  (1ULL << WIDX_NO_MONEY) |
  (1ULL << WIDX_ADD_SET_MONEY_GROUP) |
  (1ULL << WIDX_MONEY_SPINNER) |
  (1ULL << WIDX_MONEY_SPINNER_INCREMENT) |
  (1ULL << WIDX_MONEY_SPINNER_DECREMENT) |
  (1ULL << WIDX_ADD_MONEY) |
  (1ULL << WIDX_SET_MONEY) |
  (1ULL << WIDX_CLEAR_LOAN) |
  (1ULL << WIDX_DATE_SET) |
  (1ULL << WIDX_MONTH_BOX) |  // <=
  (1ULL << WIDX_MONTH_UP) |
  (1ULL << WIDX_MONTH_DOWN) |
  (1ULL << WIDX_YEAR_BOX) |
  (1ULL << WIDX_YEAR_UP) |
  (1ULL << WIDX_YEAR_DOWN) |
  (1ULL << WIDX_DAY_BOX) |
  (1ULL << WIDX_DAY_UP) |
  (1ULL << WIDX_DAY_DOWN) |
  (1ULL << WIDX_MONTH_BOX) |  // <=
  (1ULL << WIDX_DATE_GROUP) |
  (1ULL << WIDX_DATE_RESET),
  ....
};

ایک جامد تجزیہ کار کے علاوہ بہت کم لوگ اس توجہ کے امتحان کو پاس کر سکتے ہیں۔ یہ کاپی پیسٹ مثال بالکل اسی وجہ سے اچھی ہے۔

انتباہات N3

V703 یہ عجیب بات ہے کہ اخذ شدہ کلاس 'RCT12BannerElement' میں 'جھنڈے' فیلڈ بیس کلاس 'RCT12TileElementBase' میں فیلڈ کو اوور رائٹ کرتا ہے۔ لائنیں چیک کریں: RCT12.h:570, RCT12.h:259۔ libopenrct2 RCT12.h 570

struct RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};
struct rct1_peep : RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};

یقینا، بیس کلاس اور ڈیسنڈنٹ میں ایک ہی نام کے ساتھ متغیر کا استعمال ہمیشہ غلطی نہیں ہوتا۔ تاہم، وراثت کی ٹیکنالوجی خود یہ فرض کرتی ہے کہ پیرنٹ کلاس کے تمام شعبے چائلڈ کلاس میں موجود ہیں۔ وارث میں ایک ہی نام کے ساتھ قطعات کا اعلان کرکے، ہم کنفیوژن پیدا کرتے ہیں۔

وارننگ N4

V793 یہ عجیب بات ہے کہ 'imageDirection/8' بیان کا نتیجہ شرط کا ایک حصہ ہے۔ شاید، اس بیان کا موازنہ کسی اور چیز سے کیا جانا چاہیے تھا۔ libopenrct2 ObservationTower.cpp 38

void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
  if ((imageDirection / 8) && (imageDirection / 8) != 3)
  {
    ....
  }
  ....
}

آئیے قریب سے دیکھیں۔ اظہار تصویر کی سمت/8 غلط ہو جائے گا اگر تصویر کی سمت -7 سے 7 تک کی حد میں ہے۔ دوسرا حصہ: (imageDirection/8) != 3 چیک کرتا ہے تصویر کی سمت حد سے باہر ہونے کے لیے: بالترتیب -31 سے -24 اور 24 سے 31 تک۔ اس طرح سے ایک مخصوص رینج میں شامل ہونے کے لیے نمبروں کی جانچ کرنا میرے لیے کافی عجیب لگتا ہے اور، یہاں تک کہ اگر کوڈ کے اس ٹکڑے میں کوئی خامی نہیں ہے، تو میں ان شرائط کو مزید واضح کرنے کے لیے دوبارہ لکھنے کی سفارش کروں گا۔ اس سے ان لوگوں کے لیے زندگی بہت آسان ہو جائے گی جو اس کوڈ کو پڑھیں اور اسے برقرار رکھیں گے۔

وارننگ N5

V587 اس قسم کی تفویض کی ایک عجیب ترتیب: A = B؛ B = A؛۔ لائنیں چیک کریں: 1115, 1118. libopenrct2ui MouseInput.cpp 1118

void process_mouse_over(....)
{
  ....
  switch (window->widgets[widgetId].type)
  {
    case WWT_VIEWPORT:
      ebx = 0;
      edi = cursorId;                                 // <=
      // Window event WE_UNKNOWN_0E was called here,
      // but no windows actually implemented a handler and
      // it's not known what it was for
      cursorId = edi;                                 // <=
      if ((ebx & 0xFF) != 0)
      {
        set_cursor(cursorId);
        return;
      }
      break;
      ....
  }
  ....
}

یہ کوڈ کا ٹکڑا غالباً ڈی کمپائلیشن کے ذریعے حاصل کیا گیا تھا۔ پھر، بائیں تبصرے کے مطابق، غیر کام کرنے والے کوڈ کا کچھ حصہ ہٹا دیا گیا۔ تاہم، ابھی ایک دو آپریشن باقی ہیں۔ cursorId، جو بھی زیادہ معنی نہیں رکھتا ہے۔

وارننگ N6

V1004 nullptr کے خلاف تصدیق کے بعد 'پلیئر' پوائنٹر کو غیر محفوظ طریقے سے استعمال کیا گیا۔ لائنیں چیک کریں: 476, 2085. libopenrct2094 Network.cpp 2

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Add new player.
    player = AddPlayer("", "");
    if (player)                                          // <=
    {
      *player = pendingPlayer;
       if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
       {
         _serverConnection->Player = player;
       }
    }
    newPlayers.push_back(player->Id);                    // <=
  }
  ....
}

اس کوڈ کو درست کرنا کافی آسان ہے؛ آپ کو اسے تیسری بار چیک کرنے کی ضرورت ہے۔ کھلاڑی ایک null پوائنٹر پر، یا اسے مشروط بیان کے باڈی میں شامل کریں۔ میں دوسرا آپشن تجویز کروں گا:

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Add new player.
    player = AddPlayer("", "");
    if (player)
    {
      *player = pendingPlayer;
      if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
      {
        _serverConnection->Player = player;
      }
      newPlayers.push_back(player->Id);
    }
  }
  ....
}

وارننگ N7

V547 [CWE-570] اظہار 'name == nullptr' ہمیشہ غلط ہوتا ہے۔ libopenrct2 ServerList.cpp 102

std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    ....
  }
  else
  {
    ....
    entry.name = (name == nullptr ? "" : json_string_value(name));
    ....
  }
  ....
}

آپ کوڈ کی ایک مشکل سے پڑھنے والی لائن سے چھٹکارا حاصل کر سکتے ہیں اور اس کی جانچ پڑتال کے ساتھ مسئلہ حل کر سکتے ہیں nullptr. میں مندرجہ ذیل کوڈ کو تبدیل کرنے کا مشورہ دیتا ہوں:

std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    name = ""
    ....
  }
  else
  {
    ....
    entry.name = json_string_value(name);
    ....
  }
  ....
}

وارننگ N8

V1048 [CWE-1164] 'ColumnHeaderPressedCurrentState' متغیر کو ایک ہی قدر تفویض کی گئی تھی۔ libopenrct2ui CustomListView.cpp 510

void CustomListView::MouseUp(....)
{
  ....
  if (!ColumnHeaderPressedCurrentState)
  {
    ColumnHeaderPressed = std::nullopt;
    ColumnHeaderPressedCurrentState = false;
    Invalidate();
  }
}

کوڈ کافی عجیب لگتا ہے۔ مجھے ایسا لگتا ہے کہ حالت میں یا متغیر کو دوبارہ تفویض کرتے وقت ٹائپنگ کی غلطی تھی۔ کالم ہیڈر پریسڈ کرنٹ اسٹیٹ اقدار جھوٹی.

آؤٹ پٹ

جیسا کہ ہم دیکھ سکتے ہیں، PVS-Studio جامد تجزیہ کار کو آپ کے TeamCity پروجیکٹ میں ضم کرنا بہت آسان ہے۔ ایسا کرنے کے لیے، صرف ایک چھوٹی کنفیگریشن فائل لکھنا کافی ہے۔ کوڈ کو چیک کرنے سے آپ اسمبلی کے فوراً بعد مسائل کی نشاندہی کر سکیں گے، جو تبدیلیوں کی پیچیدگی اور لاگت کم ہونے پر انہیں ختم کرنے میں مدد کرے گا۔

پی وی ایس اسٹوڈیو اور مسلسل انضمام: ٹیم سٹی۔ اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ
اگر آپ انگریزی بولنے والے سامعین کے ساتھ اس مضمون کا اشتراک کرنا چاہتے ہیں، تو براہ کرم ترجمہ کا لنک استعمال کریں: Vladislav Stolyarov۔ PVS-Studio اور مسلسل انضمام: TeamCity. اوپن رولر کوسٹر ٹائکون 2 پروجیکٹ کا تجزیہ.

ماخذ: www.habr.com

نیا تبصرہ شامل کریں