1- // Copyright (c) 2016-2017 SIL International
1+ // Copyright (c) 2016-2022 SIL International
22// This software is licensed under the LGPL, version 2.1 or later
33// (http://www.gnu.org/licenses/lgpl-2.1.html)
44
5- using System . Collections . Generic ;
65using System . IO ;
76using System . Linq ;
8- using System . Net ;
9- using System . Threading ;
10- using System . Xml . Linq ;
117using Microsoft . Build . Framework ;
128
9+ // ReSharper disable once CheckNamespace
1310namespace FwBuildTasks
1411{
1512 /// <summary>
16- /// Downloads artifacts from TeamCity for the given BuildType. Select in the following order:
17- /// - If Tag or Query is specified, use them
18- /// - If VersionInfo is specified, look for a matching tag (fw-9.0.0 before fw-9.0)
19- /// - Otherwise, download .lastSuccessful
13+ /// Downloads artifacts from TeamCity for the given BuildType.
14+ /// If Tag or Query is specified, use them; otherwise, download .lastSuccessful
2015 ///
2116 /// Usage:
2217 /// <DownloadFilesFromTeamCity
2318 /// Address="http://build.palaso.org/"
2419 /// BuildType="bt2"
2520 /// Tag=".lastSuccessful"
2621 /// Query="?branch=%3Cdefault%3E"
27- /// VersionInfo="$(fwrt)/Src/MasterVersionInfo.txt"
2822 /// DownloadsDir="$(fwrt)/Downloads"
2923 /// Artifacts="@(ChorusFiles)"/>
3024 /// </summary>
3125 public class DownloadFilesFromTeamCity : DownloadFile
3226 {
3327 private const string ArtifactsUrlPart = "guestAuth/repository/download/" ;
34- private const string BuildTypeUrlPart = "guestAuth/app/rest/10.0/buildTypes/id:{0}/" ;
35- private const string BuildTagsUrlPart = "builds?locator=pinned:true&fields=build(tags(tag))" ;
3628 private const string DefaultTag = ".lastSuccessful" ;
37- private const string TagSuffix = ".tcbuildtag" ;
38- private const string QueryFormat = "?branch={0}" ;
3929
4030 /// <summary>
4131 /// TeamCity BuildType that contains the Artifacts.
@@ -50,209 +40,25 @@ public class DownloadFilesFromTeamCity : DownloadFile
5040 /// <summary>URL Query (e.g. ?branch=%3Cdefault%3E). Used only if FlexBridgeBuildType has no matching dependency.</summary>
5141 public string Query { get ; set ; }
5242
53- /// <summary>Path to FLEx's MasterVersionInfo.txt. Used to guess a build tag if unavailable through the FLExBridge BT configuration.</summary>
54- public string VersionInfo { get ; set ; }
55-
5643 /// <summary>(Semicolon-delimited) list of artifacts to download</summary>
5744 [ Required ]
5845 public string [ ] Artifacts { get ; set ; }
5946
6047 public override bool Execute ( )
6148 {
62- // If the user specified a tag or query, it overrides whatever we might find by querying TeamCity
63- if ( ! string . IsNullOrEmpty ( Tag ) || ! string . IsNullOrEmpty ( Query ) )
64- {
65- if ( string . IsNullOrEmpty ( Tag ) )
66- Tag = DefaultTag ;
67- if ( ! string . IsNullOrEmpty ( BuildType ) )
68- return DownloadAllFiles ( ) ;
69- Log . LogError ( "Cannot use a Tag or Query without a BuildType" ) ;
70- return false ;
71- }
72-
73- switch ( QueryTeamCity ( ) )
74- {
75- case TeamCityQueryResult . Found :
76- case TeamCityQueryResult . FellThrough :
77- return DownloadAllFiles ( ) ;
78- case TeamCityQueryResult . Failed :
79- return false ;
80- default :
81- Log . LogError ( "Unknown TeamCity Query Result. This is not necessarily bad, but this FwBuildTask doesn't know that." ) ;
82- return false ;
83- }
84- }
49+ if ( string . IsNullOrEmpty ( Tag ) )
50+ Tag = DefaultTag ;
8551
86- protected bool DownloadAllFiles ( )
87- {
8852 var addressBase = CombineUrl ( Address , ArtifactsUrlPart , BuildType , Tag ) ;
89- Log . LogMessage ( "Downloading artifacts from {0}{1}" , addressBase , Query == null ? null : string . Format ( " with Query {0}" , Query ) ) ;
53+ Log . LogMessage ( "Downloading artifacts from {0}{1}" , addressBase , Query == null ? null : $ " with Query { Query } " ) ;
9054 // Return success iff all files download successfully
9155 return Artifacts . Aggregate ( true , ( successSoFar , file ) => successSoFar
9256 && ProcessDownloadFile ( CombineUrl ( addressBase , file ) + Query , Path . Combine ( DownloadsDir , file ) ) ) ;
9357 }
9458
95- protected TeamCityQueryResult QueryTeamCity ( )
96- {
97- // Didn't find a matching dependency in FLExBridge; check for the most-specific version-tagged build, if any (e.g. fw-8.2.8~beta2~nightly)
98- var availableTags = GetTagsFromBuildType ( ) ;
99- if ( availableTags == null )
100- {
101- Log . LogError ( "Unable to retrieve dependencies for BuildType {0}. Check your connection and whether the BuildType exists" , BuildType ) ;
102- return TeamCityQueryResult . Failed ;
103- }
104- if ( availableTags . Any ( ) )
105- {
106- Dictionary < string , string > versionParts ;
107- if ( ! string . IsNullOrEmpty ( VersionInfo ) && BuildUtils . ParseSymbolFile ( VersionInfo , Log , out versionParts ) )
108- {
109- var tempTag = string . Format ( "fw-{0}.{1}.{2}~{3}" ,
110- versionParts [ "FWMAJOR" ] , versionParts [ "FWMINOR" ] , versionParts [ "FWREVISION" ] , versionParts [ "FWBETAVERSION" ] ) ;
111- tempTag = tempTag . Replace ( " " , "" ) . ToLowerInvariant ( ) ; // TC tags are spaceless and lowercase
112- var versionDelims = new [ ] { '.' , '~' } ;
113- var idxDelim = tempTag . LastIndexOfAny ( versionDelims ) ;
114- while ( idxDelim > 0 && ! availableTags . Contains ( tempTag ) )
115- {
116- tempTag = tempTag . Remove ( idxDelim ) ;
117- idxDelim = tempTag . LastIndexOfAny ( versionDelims ) ;
118- }
119- if ( availableTags . Contains ( tempTag ) )
120- {
121- Tag = tempTag + TagSuffix ;
122- Log . LogMessage ( "Found matching tag for BuildType {0}: {1}" , BuildType , Tag ) ;
123- if ( ! string . IsNullOrEmpty ( Query ) )
124- {
125- Log . LogWarning ( "Guessing Tags doesn't check queries. Guessed tag '{0}' for BuildType {1}, but it may not match {2}" ,
126- Tag , BuildType , Query ) ;
127- }
128- return TeamCityQueryResult . Found ;
129- }
130- }
131- }
132-
133- // REVIEW (Hasso) 2016.10: using .lastSuccessful should be a WARNING on package builds (may lead to bit rot)
134- // If all else fails, use the default "tag" .lastSuccessful
135- Tag = DefaultTag ;
136- return TeamCityQueryResult . FellThrough ;
137- }
138-
139- /// <returns>an array of tags on BuildType's pinned builds; null on any error</returns>
140- protected string [ ] GetTagsFromBuildType ( )
141- {
142- string bXml ;
143- if ( ! MakeWebRequest ( string . Format ( CombineUrl ( Address , BuildTypeUrlPart , BuildTagsUrlPart ) , BuildType ) , out bXml ) )
144- return null ;
145- var buildsElt = XDocument . Load ( new StringReader ( bXml ) ) . Element ( "builds" ) ;
146- return buildsElt == null ? null : buildsElt . Elements ( "build" ) . SelectMany ( GetTagsFromBuildElt ) . ToArray ( ) ;
147- }
148-
149- protected IEnumerable < string > GetTagsFromBuildElt ( XElement buildElt )
150- {
151- var tagsElt = buildElt . Element ( "tags" ) ;
152- if ( tagsElt == null )
153- return new string [ 0 ] ;
154- return from tagElt in tagsElt . Elements ( "tag" ) select tagElt . Attribute ( "name" ) into nameAtt where nameAtt != null select nameAtt . Value ;
155- }
156-
157- public bool MakeWebRequest ( string url , out string response )
158- {
159- response = null ;
160- for ( var retries = Retries ; retries >= 0 ; -- retries )
161- {
162- // Assign values to these objects here so that they can be referenced in the finally block
163- HttpWebResponse webResponse = null ;
164- Stream remoteStream = null ;
165- Stream errorResponseStream = null ;
166- try
167- {
168- // Create a request for the specified remote file name
169- var request = WebRequest . Create ( url ) ;
170- // If a username or password have been given, use them
171- if ( ! string . IsNullOrEmpty ( Username ) || ! string . IsNullOrEmpty ( Password ) )
172- request . Credentials = new NetworkCredential ( Username , Password ) ;
173-
174- // Prevent caching of requests so that we always download latest
175- request . Headers [ HttpRequestHeader . CacheControl ] = "no-cache" ;
176-
177- // Send the request to the server and retrieve the WebResponse object
178- webResponse = ( HttpWebResponse ) request . GetResponse ( ) ;
179- remoteStream = webResponse . GetResponseStream ( ) ;
180- if ( webResponse . StatusCode != HttpStatusCode . OK || remoteStream == null )
181- {
182- if ( webResponse . StatusCode == HttpStatusCode . OK )
183- Log . LogWarning ( "No data in response to request {0}" , url ) ;
184- else
185- Log . LogWarning ( "Unexpected Server Response[{0}] to request {1}" , webResponse . StatusCode , url ) ;
186- if ( retries > 0 )
187- {
188- Log . LogMessage ( MessageImportance . High , "Could not retrieve {0}. Trying {1} more times in {2}-minute intervals." ,
189- url , retries , RetryWaitTime / MillisPerMinute ) ;
190- Thread . Sleep ( RetryWaitTime ) ; // wait a minute
191- }
192- continue ;
193- }
194- // Once the WebResponse object has been retrieved, get the stream object associated with the response's data
195- using ( var localStream = new StreamReader ( remoteStream ) )
196- response = localStream . ReadToEnd ( ) ;
197- return true ;
198- }
199- catch ( WebException e )
200- {
201- if ( e . Response != null && ( errorResponseStream = e . Response . GetResponseStream ( ) ) != null )
202- {
203- string html ;
204- using ( var sr = new StreamReader ( errorResponseStream ) )
205- html = sr . ReadToEnd ( ) ;
206- Log . LogWarning ( "Unexpected response from {0}. Server responds {1}" , url , html ) ;
207- return false ; // The server is available, but it is likely the requested resource does not exist; don't keep trying
208- }
209- else
210- {
211- // Possibly a DNS error or some network outage between us and the server.
212- Log . LogWarning ( "No response from {0}. Exception {1}. Status {2}." , url , e . Message , e . Status ) ;
213- }
214- if ( retries > 0 )
215- {
216- Log . LogMessage ( MessageImportance . High , "Could not retrieve {0}. Trying {1} more times in {2}-minute intervals." ,
217- url , retries , RetryWaitTime / MillisPerMinute ) ;
218- Thread . Sleep ( RetryWaitTime ) ; // wait a minute
219- }
220- }
221- finally
222- {
223- // Close the response and streams objects here to make sure they're closed even if an exception is thrown at some point
224- if ( webResponse != null ) webResponse . Close ( ) ;
225- if ( remoteStream != null ) remoteStream . Close ( ) ;
226- if ( errorResponseStream != null ) errorResponseStream . Close ( ) ;
227- }
228- }
229- return false ;
230- }
231-
23259 public static string CombineUrl ( params string [ ] args )
23360 {
23461 return Path . Combine ( args ) . Replace ( '\\ ' , '/' ) ;
23562 }
236-
237- public enum TeamCityQueryResult
238- {
239- Found ,
240- Failed ,
241- FellThrough
242- }
243-
244- public struct TcDependency
245- {
246- public string BuildTypeId ;
247- public string BuildTypeName ;
248- public string RevisionValue ;
249- public string RevisionBranch ;
250-
251- public override string ToString ( )
252- {
253- return string . Format ( "{0}/{1}{2}" , BuildTypeId , RevisionValue ,
254- string . IsNullOrEmpty ( RevisionBranch ) ? null : string . Format ( QueryFormat , RevisionBranch ) ) ;
255- }
256- }
25763 }
25864}
0 commit comments