@@ -1419,13 +1419,95 @@ public abstract class ManagedSymbolModule
14191419 /// </summary>
14201420 public abstract SourceLocation SourceLocationForManagedCode ( uint methodMetadataToken , int ilOffset ) ;
14211421
1422+ /// <summary>
1423+ /// If the symbol file format supports SourceLink JSON this routine should be overriden
1424+ /// to return it.
1425+ /// </summary>
1426+ protected virtual string GetSourceLinkJson ( ) { return null ; }
1427+
14221428 #region private
1429+
14231430 protected ManagedSymbolModule ( SymbolReader reader , string path ) { _pdbPath = path ; _reader = reader ; }
14241431
14251432 internal TextWriter _log { get { return _reader . m_log ; } }
14261433
1434+ /// <summary>
1435+ /// Return a URL for 'buildTimeFilePath' using the source link mapping (that 'GetSourceLinkJson' fetched)
1436+ /// Returns null if there is URL using the SourceLink
1437+ /// </summary>
1438+ /// <param name="buildTimeFilePath"></param>
1439+ /// <returns></returns>
1440+ internal string GetUrlForFilePathUsingSourceLink ( string buildTimeFilePath )
1441+ {
1442+ if ( ! _sourceLinkMappingInited )
1443+ {
1444+ _sourceLinkMappingInited = true ;
1445+ string sourceLinkJson = GetSourceLinkJson ( ) ;
1446+ if ( sourceLinkJson != null )
1447+ _sourceLinkMapping = ParseSourceLinkJson ( sourceLinkJson ) ;
1448+ }
1449+
1450+ if ( _sourceLinkMapping != null )
1451+ {
1452+ foreach ( Tuple < string , string > map in _sourceLinkMapping )
1453+ {
1454+ string path = map . Item1 ;
1455+ string urlReplacement = map . Item2 ;
1456+
1457+ if ( buildTimeFilePath . StartsWith ( path , StringComparison . OrdinalIgnoreCase ) )
1458+ {
1459+ string tail = buildTimeFilePath . Substring ( path . Length , buildTimeFilePath . Length - path . Length ) . Replace ( '\\ ' , '/' ) ;
1460+ return urlReplacement . Replace ( "*" , tail ) ;
1461+ }
1462+ }
1463+ }
1464+ return null ;
1465+ }
1466+
1467+ /// <summary>
1468+ /// Parses SourceLink information and returns a list of filepath -> url Prefix tuples.
1469+ /// </summary>
1470+ private List < Tuple < string , string > > ParseSourceLinkJson ( string sourceLinkJson )
1471+ {
1472+ List < Tuple < string , string > > ret = null ;
1473+ // TODO this is not right for corner cases (e.g. file paths with " or , } in them)
1474+ Match m = Regex . Match ( sourceLinkJson , @"documents.?\s*:\s*{(.*?)}" , RegexOptions . Singleline ) ;
1475+ if ( m . Success )
1476+ {
1477+ string mappings = m . Groups [ 1 ] . Value ;
1478+ while ( ! string . IsNullOrWhiteSpace ( mappings ) )
1479+ {
1480+ m = Regex . Match ( m . Groups [ 1 ] . Value , "^\\ s*\" (.*?)\" \\ s*:\\ s*\" (.*?)\" \\ s*,?(.*)" , RegexOptions . Singleline ) ;
1481+ if ( m . Success )
1482+ {
1483+ if ( ret == null )
1484+ ret = new List < Tuple < string , string > > ( ) ;
1485+ string pathSpec = m . Groups [ 1 ] . Value . Replace ( "\\ \\ " , "\\ " ) ;
1486+ if ( pathSpec . EndsWith ( "*" ) )
1487+ {
1488+ pathSpec = pathSpec . Substring ( 0 , pathSpec . Length - 1 ) ; // Remove the *
1489+ ret . Add ( new Tuple < string , string > ( pathSpec , m . Groups [ 2 ] . Value ) ) ;
1490+ }
1491+ else
1492+ _log . WriteLine ( "Warning: {0} does not end in *, skipping this mapping." , pathSpec ) ;
1493+ mappings = m . Groups [ 3 ] . Value ;
1494+ }
1495+ else
1496+ {
1497+ _log . WriteLine ( "Error: Could not parse SourceLink Mapping: {0}" , mappings ) ;
1498+ break ;
1499+ }
1500+ }
1501+ }
1502+ else
1503+ _log . WriteLine ( "Error: Could not parse SourceLink Json: {0}" , sourceLinkJson ) ;
1504+ return ret ;
1505+ }
1506+
14271507 string _pdbPath ;
14281508 SymbolReader _reader ;
1509+ List < Tuple < string , string > > _sourceLinkMapping ; // Used by SourceLink to map build paths to URLs (see GetUrlForFilePath)
1510+ bool _sourceLinkMappingInited ; // Lazy init flag.
14291511 #endregion
14301512 }
14311513
@@ -1489,36 +1571,7 @@ public abstract class SourceFile
14891571 /// can be used to fetch it with HTTP Get), then return that Url. If no such publishing
14901572 /// point exists this property will return null.
14911573 /// </summary>
1492- public virtual string Url { get { return null ; } }
1493-
1494- /// <summary>
1495- /// Look up the source from the source server. Returns null if it can't find the source
1496- /// By default this simply uses the Url to look it up on the web. If 'Url' returns null
1497- /// so does this.
1498- /// </summary>
1499- public virtual string GetSourceFromSrcServer ( )
1500- {
1501- // Search the SourceLink url location
1502- string url = Url ;
1503- if ( url != null )
1504- {
1505- HttpClient httpClient = new HttpClient ( ) ;
1506- HttpResponseMessage response = httpClient . GetAsync ( url ) . Result ;
1507-
1508- response . EnsureSuccessStatusCode ( ) ;
1509- Stream content = response . Content . ReadAsStreamAsync ( ) . Result ;
1510- string cachedLocation = GetCachePathForUrl ( url ) ;
1511- if ( cachedLocation != null )
1512- {
1513- using ( FileStream file = File . Create ( _filePath ) )
1514- content . CopyTo ( file ) ;
1515- return cachedLocation ;
1516- }
1517- else
1518- _log . WriteLine ( "Warning: SourceCache not set, giving up fetching source from the network." ) ;
1519- }
1520- return null ;
1521- }
1574+ public virtual string Url { get { return _symbolModule . GetUrlForFilePathUsingSourceLink ( BuildTimeFilePath ) ; } }
15221575
15231576 /// <summary>
15241577 /// This may fetch things from the source server, and thus can be very slow, which is why it is not a property.
@@ -1609,10 +1662,40 @@ public virtual string GetSourceFile(bool requireChecksumMatch = false)
16091662
16101663 protected TextWriter _log { get { return _symbolModule . _log ; } }
16111664
1665+ /// <summary>
1666+ /// Look up the source from the source server. Returns null if it can't find the source
1667+ /// By default this simply uses the Url to look it up on the web. If 'Url' returns null
1668+ /// so does this.
1669+ /// </summary>
1670+ protected virtual string GetSourceFromSrcServer ( )
1671+ {
1672+ // Search the SourceLink url location
1673+ string url = Url ;
1674+ if ( url != null )
1675+ {
1676+ HttpClient httpClient = new HttpClient ( ) ;
1677+ HttpResponseMessage response = httpClient . GetAsync ( url ) . Result ;
1678+
1679+ response . EnsureSuccessStatusCode ( ) ;
1680+ Stream content = response . Content . ReadAsStreamAsync ( ) . Result ;
1681+ string cachedLocation = GetCachePathForUrl ( url ) ;
1682+ if ( cachedLocation != null )
1683+ {
1684+ Directory . CreateDirectory ( Path . GetDirectoryName ( cachedLocation ) ) ;
1685+ using ( FileStream file = File . Create ( cachedLocation ) )
1686+ content . CopyTo ( file ) ;
1687+ return cachedLocation ;
1688+ }
1689+ else
1690+ _log . WriteLine ( "Warning: SourceCache not set, giving up fetching source from the network." ) ;
1691+ }
1692+ return null ;
1693+ }
1694+
16121695 /// <summary>
16131696 /// Given 'fileName' which is a path to a file (which may not exist), set
16141697 /// _filePath and _checksumMatches appropriately. Namely _filePath should
1615- /// always be the 'best' candidate for the soruce file path (matching checksum
1698+ /// always be the 'best' candidate for the source file path (matching checksum
16161699 /// wins, otherwise first existing file wins).
16171700 ///
16181701 /// Returns true if we have a perfect match (no additional probing needed).
0 commit comments