@@ -36,7 +36,6 @@ enum BazelTargetQuerierParserError: Error, LocalizedError {
3636 case unexpectedTargetType( Int )
3737 case noTopLevelTargets( [ TopLevelRuleType ] )
3838 case missingPathExtension( String )
39- case unexpectedFileExtension( String )
4039
4140 var errorDescription : String ? {
4241 switch self {
@@ -61,7 +60,6 @@ enum BazelTargetQuerierParserError: Error, LocalizedError {
6160 \( rules. map { $0. rawValue } . joined ( separator: " , " ) )
6261 """
6362 case . missingPathExtension( let path) : return " Missing path extension for \( path) "
64- case . unexpectedFileExtension( let pathExtension) : return " Unexpected file extension: \( pathExtension) "
6563 }
6664 }
6765}
@@ -74,6 +72,7 @@ protocol BazelTargetQuerierParser: AnyObject {
7472 supportedDependencyRuleTypes: [ DependencyRuleType ] ,
7573 supportedTopLevelRuleTypes: [ TopLevelRuleType ] ,
7674 rootUri: String ,
75+ workspaceName: String ,
7776 executionRoot: String ,
7877 toolchainPath: String ,
7978 ) throws -> ProcessedCqueryResult
@@ -93,17 +92,12 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
9392 supportedDependencyRuleTypes: [ DependencyRuleType ] ,
9493 supportedTopLevelRuleTypes: [ TopLevelRuleType ] ,
9594 rootUri: String ,
95+ workspaceName: String ,
9696 executionRoot: String ,
9797 toolchainPath: String ,
9898 ) throws -> ProcessedCqueryResult {
9999 let cquery = try BazelProtobufBindings . parseCqueryResult ( data: data)
100- let targets = cquery. results
101- . map { $0. target }
102- . filter {
103- // Ignore external labels.
104- // FIXME: I guess _technically_ we could index those, but skipping for now.
105- return !$0. rule. name. hasPrefix ( " @ " )
106- }
100+ let targets = cquery. results. map { $0. target }
107101
108102 let supportedDependencyRuleTypesSet = Set ( supportedDependencyRuleTypes)
109103 let testBundleRulesSet = Set ( testBundleRules)
@@ -184,7 +178,7 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
184178 let label = target. rule. name
185179 depLabelToUriMap [ label] = (
186180 BuildTargetIdentifier (
187- uri: try label. toTargetId ( rootUri: rootUri)
181+ uri: try label. toTargetId ( rootUri: rootUri, workspaceName : workspaceName , executionRoot : executionRoot )
188182 ) , label
189183 )
190184 }
@@ -219,7 +213,7 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
219213 }
220214 depLabelToUriMap [ label] = (
221215 BuildTargetIdentifier (
222- uri: try realLabel. toTargetId ( rootUri: rootUri)
216+ uri: try realLabel. toTargetId ( rootUri: rootUri, workspaceName : workspaceName , executionRoot : executionRoot )
223217 ) , realLabel
224218 )
225219 }
@@ -233,9 +227,9 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
233227 }
234228
235229 let rule = target. rule
236- let idUri : URI = try rule. name. toTargetId ( rootUri: rootUri)
230+ let idUri : URI = try rule. name. toTargetId ( rootUri: rootUri, workspaceName : workspaceName , executionRoot : executionRoot )
237231 let id = BuildTargetIdentifier ( uri: idUri)
238- let baseDirectory : URI = try rule . name . toBaseDirectory ( rootUri : rootUri )
232+ let baseDirectory : URI ? = idUri . toBaseDirectory ( )
239233
240234 let sourcesItem = try processSrcsAttr (
241235 rule: rule,
@@ -377,17 +371,10 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
377371 let hdrsAttribute = rule. attribute. first { $0. name == " hdrs " } ? . stringListValue ?? [ ]
378372 let srcs : [ URI ] = ( srcsAttribute + hdrsAttribute) . compactMap {
379373 guard let srcUri = srcToUriMap [ $0] else {
380- // If the file is not part of the original array provided to this function,
381- // then this is likely a generated file.
382- // FIXME: Generated files are handled by the `generated file` mmnemonic,
383- // which we don't handle today. Ignoring them for now.
384- logger. debug (
385- " Skipping \( $0, privacy: . public) : Source does not exist, most likely a generated file. "
386- )
387374 return nil
388375 }
389376 return srcUri
390- } . sorted ( by : { $0 . stringValue < $1 . stringValue } )
377+ }
391378 return SourcesItem (
392379 target: targetId,
393380 sources: try srcs. map {
@@ -408,12 +395,18 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
408395 guard let pathExtension = src. fileURL? . pathExtension else {
409396 throw BazelTargetQuerierParserError . missingPathExtension ( src. stringValue)
410397 }
411- guard let extensionKind = SupportedExtension ( rawValue: pathExtension) else {
412- throw BazelTargetQuerierParserError . unexpectedFileExtension ( pathExtension)
398+ let kind : SourceKitSourceItemKind
399+ let language : Language ?
400+
401+ if let extensionKind = SupportedExtension ( rawValue: pathExtension) {
402+ kind = extensionKind. kind
403+ language = extensionKind. language
404+ } else {
405+ logger. error ( " Unexpected file extension \( pathExtension) for \( src. stringValue) . Will recover by setting `language` to `nil`. " )
406+ kind = . source
407+ language = nil
413408 }
414409
415- let kind : SourceKitSourceItemKind = extensionKind. kind
416- let language : Language = extensionKind. language
417410 let copyDestinations = srcCopyDestinations ( for: src, rootUri: rootUri, executionRoot: executionRoot)
418411
419412 return SourceItem (
@@ -468,16 +461,14 @@ final class BazelTargetQuerierParserImpl: BazelTargetQuerierParser {
468461 let attrName = isBuildTestRule ? " targets " : " deps "
469462 let thisRule = rule. name
470463 let depsAttribute = rule. attribute. first { $0. name == attrName } ? . stringListValue ?? [ ]
471- return depsAttribute. compactMap { label in
464+ let implDeps = rule. attribute. first { $0. name == " implementation_deps " } ? . stringListValue ?? [ ]
465+ return ( depsAttribute + implDeps) . compactMap { label in
472466 guard let ( depUri, depRealLabel) = depLabelToUriMap [ label] else {
473- logger. debug (
474- " Skipping dependency \( label, privacy: . public) : not considered a valid dependency "
475- )
476467 return nil
477468 }
478469 dependencyGraph [ thisRule, default: [ ] ] . append ( depRealLabel)
479470 return depUri
480- } . sorted ( by : { $0 . uri . stringValue < $1 . uri . stringValue } )
471+ }
481472 }
482473
483474 private func traverseGraph(
@@ -625,51 +616,62 @@ extension Array {
625616extension String {
626617 /// Converts a Bazel label into a URI and returns a unique target id.
627618 ///
628- /// file://<path-to-root>/<package-name>___<target-name>
619+ /// For local labels: file://<path-to-root>/<package-name>___<target-name>
620+ /// For external labels: file://<execution-root>/external/<repo-name>/<package-name>___<target-name>
629621 ///
630- fileprivate func toTargetId( rootUri: String ) throws -> URI {
631- let ( packageName, targetName) = try splitTargetLabel ( )
632- let path = " file:// " + rootUri + " / " + packageName + " ___ " + targetName
622+ fileprivate func toTargetId( rootUri: String , workspaceName: String , executionRoot: String ) throws -> URI {
623+ let ( repoName, packageName, targetName) = try splitTargetLabel ( workspaceName: workspaceName)
624+ let packagePath = packageName. isEmpty ? " " : " / " + packageName
625+ let path : String
626+ if repoName == workspaceName {
627+ path = " file:// " + rootUri + packagePath + " / " + targetName
628+ } else {
629+ // External repo: use execution root + external path
630+ path = " file:// " + executionRoot + " /external/ " + repoName + packagePath + " / " + targetName
631+ }
633632 guard let uri = try ? URI ( string: path) else {
634633 throw BazelTargetQuerierParserError . convertUriFailed ( path)
635634 }
636635 return uri
637636 }
638637
639- /// Fetches the base directory of a target based on its id.
640- ///
641- /// file://<path-to-root>/<package-name>
642- ///
643- fileprivate func toBaseDirectory( rootUri: String ) throws -> URI {
644- let ( packageName, _) = try splitTargetLabel ( )
645-
646- let fileScheme = " file:// " + rootUri + " / " + packageName
647-
648- guard let uri = try ? URI ( string: fileScheme) else {
649- throw BazelTargetQuerierParserError . convertUriFailed ( fileScheme)
650- }
651-
652- return uri
653- }
654-
655- /// Splits a full Bazel label into a tuple of its package and target names.
656- fileprivate func splitTargetLabel( ) throws -> ( packageName: String , targetName: String ) {
638+ /// Splits a full Bazel label into a tuple of its repo, package, and target names.
639+ /// For local labels (//package:target), the repo name is the provided workspace name.
640+ /// For external labels (@repo//package:target), the repo name is extracted.
641+ fileprivate func splitTargetLabel( workspaceName: String ) throws -> ( repoName: String , packageName: String , targetName: String ) {
657642 let components = split ( separator: " : " )
658643
659644 guard components. count == 2 else {
660645 throw BazelTargetQuerierParserError . incorrectName ( self )
661646 }
662647
663- let packageName =
664- if components [ 0 ] . starts ( with: " // " ) {
665- String ( components [ 0 ] . dropFirst ( 2 ) )
666- } else {
667- String ( components [ 0 ] )
668- }
669-
648+ let repoAndPackage = components [ 0 ]
670649 let targetName = String ( components [ 1 ] )
671650
672- return ( packageName: packageName, targetName: targetName)
651+ let repoName : String
652+ let packageName : String
653+
654+ if repoAndPackage. hasPrefix ( " @// " ) {
655+ // Alias for the main repo.
656+ repoName = workspaceName
657+ packageName = String ( repoAndPackage. dropFirst ( 3 ) )
658+ } else if repoAndPackage. hasPrefix ( " // " ) {
659+ // Also the main repo.
660+ repoName = workspaceName
661+ packageName = String ( repoAndPackage. dropFirst ( 2 ) )
662+ } else if repoAndPackage. hasPrefix ( " @ " ) && repoAndPackage. contains ( " // " ) {
663+ // External label
664+ let withoutAt = repoAndPackage. dropFirst ( )
665+ guard let slashIndex = withoutAt. firstIndex ( of: " / " ) else {
666+ throw BazelTargetQuerierParserError . incorrectName ( self )
667+ }
668+ repoName = String ( withoutAt [ ..< slashIndex] )
669+ packageName = String ( withoutAt [ slashIndex... ] . dropFirst ( 2 ) )
670+ } else {
671+ throw BazelTargetQuerierParserError . incorrectName ( self )
672+ }
673+
674+ return ( repoName: repoName, packageName: packageName, targetName: targetName)
673675 }
674676
675677 // Converts a Bazel label to its "full" equivalent, if needed.
@@ -686,3 +688,13 @@ extension String {
686688 }
687689 }
688690}
691+
692+ extension URI {
693+ /// Fetches the base directory of a target by dropping the last path component (target name) from the URI.
694+ fileprivate func toBaseDirectory( ) -> URI ? {
695+ guard let url = fileURL else {
696+ return nil
697+ }
698+ return URI ( url. deletingLastPathComponent ( ) )
699+ }
700+ }
0 commit comments