рдХреЗрдХ рдФрд░ рдЯреАрдорд╕рд┐рдЯреА рдЗрдВрдЯреАрдЧреНрд░реЗрд╢рди

рдХреЗрдХ рдЖрдкрдХреЗ рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡рд┐рддрд░рдг рдкрд╛рдЗрдкрд▓рд╛рдЗрди рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдорд╣рд╛рди рдЙрдкрдХрд░рдг рд╣реИред рдореИрдВ рдЙрд╕рд╕реЗ рдкреНрдпрд╛рд░ рдХрд░рддрд╛ рд╣реВрдВ рдХреНрдпреЛрдВрдХрд┐ рд╡рд╣ рдореБрдЭреЗ рдЗрд╕ рдкрд╛рдЗрдкрд▓рд╛рдЗрди рдХреЛ рд╕реА # рдореЗрдВ рд▓рд┐рдЦрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ, рдЬрд┐рд╕реЗ рдореИрдВ рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рдЬрд╛рдирддрд╛ рд╣реВрдВред рдХреЗрдХ, рдкреАрдПрд╕рдХреЗ рдФрд░ рдЕрдиреНрдп рд╕рдорд╛рди рд░реВрдкрд░реЗрдЦрд╛рдУрдВ рдХреА рдПрдХ рдмрдбрд╝реА рд╡рд┐рд╢реЗрд╖рддрд╛ рдпрд╣ рд╣реИ рдХрд┐ рд╡реЗ рдПрдХ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдмрдирд╛рддреЗ рд╣реИрдВ рдЬреЛ рдбреЗрд╡рд▓рдкрд░ рдХреА рд╕реНрдерд╛рдиреАрдп рдорд╢реАрди рдФрд░ рд╕реАрдЖрдИ рд╕рд░реНрд╡рд░ рдкрд░ рджреЛрдиреЛрдВ рдХреЛ рдЪрд▓рд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдпрд╣рд╛рдВ рдореИрдВ рд╕рдордЭрд╛рдКрдВрдЧрд╛ рдХрд┐ рдХреИрд╕реЗ рдЯреАрдо рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ рд╕рд╛рде рдХреЗрдХ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЗрдВрдЯрд░реИрдХреНрд╢рди рдХреЛ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПред


рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ


рдореИрдВ рдорд╛рди рд▓реВрдВрдЧрд╛ рдХрд┐ рдЖрдкрдХреЛ рдХреЗрдХ рдФрд░ рдЯреАрдорд╕рд┐рдЯреА рдХрд╛ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдЬреНрдЮрд╛рди рд╣реИред рдЕрдиреНрдпрдерд╛, рдЖрдк рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреЛ рдкрдврд╝рдХрд░ рд╢реБрд░реВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:


рдХреЗрдХ рдХреЗ рд▓рд┐рдП:



рдЯреАрдо рдХреЗ рд▓рд┐рдП:



рдЕрдм рдмрд╛рдд рдХрд░рддреЗ рд╣реИрдВ рдХреЗрдХ рдФрд░ рдЯреАрдорд╕рд┐рдЯреА рдХреА рдмрд╛рддрдЪреАрдд рдХреАред


рд▓реЙрдЧрд┐рдВрдЧ


рдХреЗрдХ рдкрд╛рдЗрдкрд▓рд╛рдЗрди рдореЗрдВ рдЖрдорддреМрд░ рдкрд░ рдХрдИ рдХрд╛рд░реНрдп рд╣реЛрддреЗ рд╣реИрдВред рдЯреАрдорд╕реАрдЯреА рд▓реЙрдЧ рдореЗрдВ рдРрд╕реЗ рдкреНрд░рддреНрдпреЗрдХ рдХрд╛рд░реНрдп рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрд▓рдЧ рдЕрдиреБрднрд╛рдЧ рд╣реЛрдирд╛ рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рд╣реЛрдЧрд╛ред рдореИрдВ рд▓реЙрдЧ рдореЗрдВ рдкреНрд░рддреНрдпреЗрдХ рдХреЗрдХ рдХрд╛рд░реНрдп рдХреЗ рд▓рд┐рдП рдПрдХ рдмрдВрдзрдиреЗрд╡рд╛рд▓рд╛ рдЕрдиреБрднрд╛рдЧ рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ:


рдкрддреНрд░рд┐рдХрд╛


рдХреЗрдХ рдПрдкреАрдЖрдИ рдореЗрдВ TeamCity.WriteStartBuildBlock рдФрд░ TeamCity.WriteEndBuildBlock рддрд░реАрдХреЗ рд╣реИрдВред рдпрджреНрдпрдкрд┐ рд╣рд░ рдХрд╛рд░реНрдп рдореЗрдВ рдЙрдирдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рд╕рдВрднрд╡ рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд╣реЛ рд╕рдХрддрд╛ рд╣реИред рдХреЗрдХ рдореЗрдВ рдЯрд╛рд╕реНрдХрд╕реЗрдЯрдЕрдк рдФрд░ рдЯрд╛рд╕реНрдХрдЖрд░реНрдЯрдбрд╛рдЙрди рд╡рд┐рдзрд┐рдпрд╛рдВ рд╣реИрдВ рдЬрд┐рдиреНрд╣реЗрдВ рдкреНрд░рддреНрдпреЗрдХ рдХрд╛рд░реНрдп рд╕реЗ рдкрд╣рд▓реЗ рдФрд░ рдмрд╛рдж рдореЗрдВ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИред рд╡реЗ рдЯреАрдорд╕рд┐рдЯреА рд▓реЙрдЧ рдмреНрд▓реЙрдХ рдЦреЛрд▓ рдФрд░ рдмрдВрдж рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:


TaskSetup(setupContext => { if(TeamCity.IsRunningOnTeamCity) { TeamCity.WriteStartBuildBlock(setupContext.Task.Name); } }); TaskTeardown(teardownContext => { if(TeamCity.IsRunningOnTeamCity) { TeamCity.WriteEndBuildBlock(teardownContext.Task.Name); } }); 

рдпрд╣рд╛рдБ, TeamCity.IsRunningOnTeamCity рдЧреБрдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдпрд╣ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдХрд┐ TeamCity рдХрд╛ рдХреЛрдб рдкреЙрдкрдЕрдк рд╣реИ рдпрд╛ рдирд╣реАрдВред


рдЕрдм рд╣рдорд╛рд░реА рдкрддреНрд░рд┐рдХрд╛ рдореЗрдВ рдкреНрд░рддреНрдпреЗрдХ рдХрд╛рд░реНрдп рдХреЗ рд▓рд┐рдП рдмрдВрдзрдиреЗрд╡рд╛рд▓рд╛ рдмреНрд▓реЙрдХ рд╣реИрдВред рд▓реЗрдХрд┐рди рдПрдХ рдФрд░ рд╕реБрдзрд╛рд░ рдЬреЛрдбрд╝рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред


рдЖрдорддреМрд░ рдкрд░, рдХреЗрдХ рдореЗрдВ рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рдЫреЛрдЯреЗ рдирд╛рдо рд╣реЛрддреЗ рд╣реИрдВ: рдмрд┐рд▓реНрдб , рдЯреЗрд╕реНрдЯ , рдХреНрд▓реАрди ред рдЗрд╕рд▓рд┐рдП рдЙрдиреНрд╣реЗрдВ рдХрдорд╛рдВрдб рд▓рд╛рдЗрди рд╕реЗ рднрд╛рдЧрдирд╛ рдЖрд╕рд╛рди рд╣реЛрддрд╛ рд╣реИред рд▓реЗрдХрд┐рди TeamCity рдореЗрдВ, рдореИрдВ рдФрд░ рдЕрдзрд┐рдХ рд╡рд┐рд╕реНрддреГрдд рдХреЗрдХ рдХрд╛рд░реНрдп рд╡рд┐рд╡рд░рдг рд╣реЛрдЧрд╛ред рдФрд░ рдРрд╕рд╛ рдХрд░рдирд╛ рд╕рдВрднрд╡ рд╣реИред рдХрд╛рд░реНрдп рдХреЛ рд╡рд┐рд╡рд░рдг рджреЗрдиреЗ рдХреЗ рд▓рд┐рдП, рд╡рд░реНрдгрди рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ:


 Task("Clean") .Description("Create and clean folders with results") .Does(() => { ... }); 

рдЕрдм рдЗрди рд╡рд┐рд╡рд░рдгреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рд▓реЙрдЧ рдореЗрдВ рдмреНрд▓реЙрдХ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ:


 TaskSetup(setupContext => { if(TeamCity.IsRunningOnTeamCity) { TeamCity.WriteStartBuildBlock(setupContext.Task.Description ?? setupContext.Task.Name); } }); TaskTeardown(teardownContext => { if(TeamCity.IsRunningOnTeamCity) { TeamCity.WriteEndProgress(teardownContext.Task.Description ?? teardownContext.Task.Name); } }); 

рдЗрд╕рд╕реЗ рдЗрд╕рдХреА рдкрдардиреАрдпрддрд╛ рдореЗрдВ рд╕реБрдзрд╛рд░ рд╣реЛрддрд╛ рд╣реИред


рдкреНрд░рдЧрддрд┐ рд╣реБрдИ


рдпрджрд┐ рдХреЗрдХ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдВ рд▓рдВрдмрд╛ рд╕рдордп рд▓рдЧрддрд╛ рд╣реИ, рддреЛ рдпрд╣ рдЬрд╛рдирдирд╛ рдЙрдкрдпреЛрдЧреА рд╣реИ рдХрд┐ рд╡рд░реНрддрдорд╛рди рдореЗрдВ рдХреМрди рд╕рд╛ рдХрд╛рд░реНрдп рдЪрд▓ рд░рд╣рд╛ рд╣реИред


рдкреНрд░рдЧрддрд┐ рдХреЗ рд╕рдВрдХреЗрдд


рдпрд╣ TeamCity.WriteStartProgress рдФрд░ TeamCity.WriteEndProgress рддрд░реАрдХреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкреНрд░рд╛рдкреНрдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдЙрдирдХреЗ рдХреЙрд▓ рдХреЛ рдПрдХ рд╣реА рдЯрд╛рд╕реНрдХрд╕реЗрдЯрдЕрдк рдФрд░ рдЯрд╛рд╕реНрдХрдЖрд░реНрдЯрдбрд╛рдЙрди рдореЗрдВ рдбрд╛рд▓рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ:


 TaskSetup(setupContext => { if(TeamCity.IsRunningOnTeamCity) { TeamCity.WriteStartBuildBlock(setupContext.Task.Description ?? setupContext.Task.Name); TeamCity.WriteStartProgress(setupContext.Task.Description ?? setupContext.Task.Name); } }); TaskTeardown(teardownContext => { if(TeamCity.IsRunningOnTeamCity) { TeamCity.WriteEndProgress(teardownContext.Task.Description ?? teardownContext.Task.Name); TeamCity.WriteEndBuildBlock(teardownContext.Task.Description ?? teardownContext.Task.Name); } }); 

рдкрд░реАрдХреНрд╖рдг рдХреЗ рдкрд░рд┐рдгрд╛рдо


рдпрджрд┐ рдЖрдк рдХреЗрдХ рдХрд╛рд░реНрдп рдореЗрдВ рдкрд░реАрдХреНрд╖рдг рдЪрд▓рд╛рддреЗ рд╣реИрдВ, рддреЛ рдЯреАрдорд╕рд┐рдЯреА рдЖрдкрдХреЛ рдЙрдирдХреЗ рдкрд░рд┐рдгрд╛рдо рджрд┐рдЦрд╛рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реИред



рдпрд╣ TeamCity.ImportData рдкрджреНрдзрддрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдпрд╣ рджреЛ рдкреИрд░рд╛рдореАрдЯрд░ рд▓реЗрддрд╛ рд╣реИ: рдбреЗрдЯрд╛ рдкреНрд░рдХрд╛рд░ рдХрд╛ рдПрдХ рд╕реНрдЯреНрд░рд┐рдВрдЧ рд╡рд┐рд╡рд░рдг рдФрд░ рдЗрд╕ рдбреЗрдЯрд╛ рд╡рд╛рд▓реА рдлрд╝рд╛рдЗрд▓ рдХрд╛ рдкрдеред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрджрд┐ рдЖрдк MSTest рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдпрд╣рд╛рдВ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рдкрд░рд┐рдгрд╛рдореЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЯреАрдорд╕рд┐рдЯреА рдХреЛ рдХреИрд╕реЗ рдмрддрд╛рдпрд╛ рдЬрд╛рдП:


 Task("Run-Tests") .Description("Run tests") .IsDependentOn("Clean") .IsDependentOn("Build") .Does(() => { var testDllsPattern = string.Format("./**/bin/{0}/*.*Tests.dll", configuration); var testDlls = GetFiles(testDllsPattern); var testResultsFile = System.IO.Path.Combine(temporaryFolder, "testResults.trx"); MSTest(testDlls, new MSTestSettings() { ResultsFile = testResultsFile }); if(TeamCity.IsRunningOnTeamCity) { TeamCity.ImportData("mstest", testResultsFile); } }); 

рдЯреАрдорд╕рд┐рдЯреА рдХрдИ рддрд░рд╣ рдХреЗ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддреА рд╣реИред Mestest рдХреЗ рдЕрд▓рд╛рд╡рд╛ , рдЖрдк nunit , vstest рдФрд░ рдХреБрдЫ рдЕрдиреНрдп рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ ред


рдЯреЗрд╕реНрдЯ рдХрд╡рд░реЗрдЬ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдХреЛрдб


рдЯреАрдорд╕рд┐рдЯреА рдкрд░реАрдХреНрд╖рдг рджреНрд╡рд╛рд░рд╛ рдХреЛрдб рдХрд╡рд░реЗрдЬ рдХреЗ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдХреЗ рдкрд░рд┐рдгрд╛рдо рдХреЛ рджрд┐рдЦрд╛рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реИред



TeamCity рд╡рд░реНрддрдорд╛рди рдореЗрдВ DotCover рдХреЗ рд╕рд╛рде рдПрдХреАрдХрд░рдг рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИред рдореБрдЭреЗ рдПрдХ рдХреЗрдХ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдВ DotCover рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХрд╛ рддрд░реАрдХрд╛ рдмрддрд╛рддреЗ рд╣реИрдВред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, DotCover рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:


 #tool "nuget:?package=JetBrains.dotCover.CommandLineTools" 

рдЙрд╕рдХреЗ рдмрд╛рдж, рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд╛рд░реНрдпреЛрдВ рдореЗрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ:


 Task("Analyse-Test-Coverage") .Description("Analyse code coverage by tests") .IsDependentOn("Clean") .IsDependentOn("Build") .Does(() => { var coverageResultFile = System.IO.Path.Combine(temporaryFolder, "coverageResult.dcvr"); var testDllsPattern = string.Format("./**/bin/{0}/*.*Tests.dll", configuration); var testDlls = GetFiles(testDllsPattern); var testResultsFile = System.IO.Path.Combine(temporaryFolder, "testResults.trx"); DotCoverCover(tool => { tool.MSTest(testDlls, new MSTestSettings() { ResultsFile = testResultsFile }); }, new FilePath(coverageResultFile), new DotCoverCoverSettings() .WithFilter("+:Application") .WithFilter("-:Application.*Tests") ); if(TeamCity.IsRunningOnTeamCity) { TeamCity.ImportData("mstest", testResultsFile); TeamCity.ImportDotCoverCoverage(coverageResultFile); } }); 

рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдпрд╣рд╛рдВ рдкрд░реАрдХреНрд╖рдг рднреА рдЪрд▓рд╛рдП рдЬрд╛рддреЗ рд╣реИрдВред рдЗрд╕рд▓рд┐рдП, рд╣рдо рдЯреАрдо рдХреЗ рдкрд░реАрдХреНрд╖рдг рдХреЗ рдкрд░рд┐рдгрд╛рдореЛрдВ рдФрд░ рдЙрдирдХреЗ рдХреЛрдб рдХрд╡рд░реЗрдЬ рдХреЗ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдХреЗ рдкрд░рд┐рдгрд╛рдореЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рддреБрд░рдВрдд рд╕реВрдЪрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред TeamCity.ImportDotCoverCoverage рд╡рд┐рдзрд┐ рдпрд╣реА рдХрд░рддреА рд╣реИред


рдХрд▓рд╛рдХреГрддрд┐ рдкреНрд░рдХрд╛рд╢рди


TeamCity рдореЗрдВ, рдЖрдк рдХреЗрдХ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ рд╕рдВрдЪрд╛рд▓рди рдХреЗ рджреМрд░рд╛рди рдмрдирд╛рдИ рдЧрдИ рдХреБрдЫ рдХрд▓рд╛рдХреГрддрд┐рдпреЛрдВ рдХреЛ рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЗрд╕ рднреВрдорд┐рдХрд╛ рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрдЪреНрдЫрд╛ рдЙрдореНрдореАрджрд╡рд╛рд░ NuGet рдкреИрдХреЗрдЬ рд╣реИрдВ:



рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЙрди рд╕рднреА рдХрд▓рд╛рдХреГрддрд┐рдпреЛрдВ рдХреЛ рд░рдЦреЗрдВ рдЬрд┐рдиреНрд╣реЗрдВ рдЖрдк рдПрдХ рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВред рддреЛ рдЖрдк TeamCity.PublishArtifacts рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:


 Task("Publish-Artifacts-On-TeamCity") .Description("Publish artifacts on TeamCity") .IsDependentOn("Create-NuGet-Package") .WithCriteria(TeamCity.IsRunningOnTeamCity) .Does(() => { TeamCity.PublishArtifacts(artifactsFolder); }); 

рдирд┐рд╖реНрдХрд░реНрд╖


рдЙрдореНрдореАрдж рд╣реИ рдХрд┐ рдпреЗ рд╕рд░рд▓ рдХреЛрдб рдЙрджрд╛рд╣рд░рдг рдЖрдкрдХреЗ рд╕рдордп рдФрд░ рдкреНрд░рдпрд╛рд╕ рдХреЛ рдмрдЪрд╛рдПрдВрдЧреЗ рдпрджрд┐ рдЖрдк рдЪрд╛рд╣рддреЗ рд╣реИрдВ рдХрд┐ рдЖрдкрдХреА рдХреЗрдХ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЯреАрдорд╕рд┐рдЯреА рдкрд░ рдЪрд▓реЗред рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХрд╛ рдкреВрд░рд╛ рд╕рдВрд╕реНрдХрд░рдг рдФрд░ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдЬреЛ рдЗрд╕реЗ рдкреНрд░реЛрд╕реЗрд╕ рдХрд░рддрд╛ рд╣реИ, рдЙрд╕реЗ GitHub рдкрд░ рдкрд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ ред рд╕реМрднрд╛рдЧреНрдп рд╣реИ

Source: https://habr.com/ru/post/hi432814/


All Articles