@@ -1419,13 +1419,95 @@ public abstract class ManagedSymbolModule
1419
1419
/// </summary>
1420
1420
public abstract SourceLocation SourceLocationForManagedCode ( uint methodMetadataToken , int ilOffset ) ;
1421
1421
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
+
1422
1428
#region private
1429
+
1423
1430
protected ManagedSymbolModule ( SymbolReader reader , string path ) { _pdbPath = path ; _reader = reader ; }
1424
1431
1425
1432
internal TextWriter _log { get { return _reader . m_log ; } }
1426
1433
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
+
1427
1507
string _pdbPath ;
1428
1508
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.
1429
1511
#endregion
1430
1512
}
1431
1513
@@ -1489,36 +1571,7 @@ public abstract class SourceFile
1489
1571
/// can be used to fetch it with HTTP Get), then return that Url. If no such publishing
1490
1572
/// point exists this property will return null.
1491
1573
/// </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 ) ; } }
1522
1575
1523
1576
/// <summary>
1524
1577
/// 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)
1609
1662
1610
1663
protected TextWriter _log { get { return _symbolModule . _log ; } }
1611
1664
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
+
1612
1695
/// <summary>
1613
1696
/// Given 'fileName' which is a path to a file (which may not exist), set
1614
1697
/// _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
1616
1699
/// wins, otherwise first existing file wins).
1617
1700
///
1618
1701
/// Returns true if we have a perfect match (no additional probing needed).
0 commit comments