@@ -10,6 +10,7 @@ import (
1010 "path"
1111 "path/filepath"
1212 "sort"
13+ "strings"
1314
1415 "github.com/bazelbuild/bazel-gazelle/config"
1516 "github.com/bazelbuild/bazel-gazelle/label"
@@ -161,6 +162,11 @@ func (*gazelleCabalLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *re
161162 toolRepo := packageRepo + "-exe"
162163 if imports != nil {
163164 importData := imports .(ImportData )
165+
166+ // Consolidate all ImportData fields to avoid duplicate selects
167+ importData .Deps = consolidateConfigurableList (importData .Deps )
168+ importData .GhcOpts = consolidateConfigurableList (importData .GhcOpts )
169+ importData .ExtraLibraries = consolidateConfigurableList (importData .ExtraLibraries )
164170
165171 libraryLabels , unresolvedExtraLibraries := getExtraLibraryLabels (c , importData .ExtraLibraries )
166172 setDepsAndPluginsAttributes (libraryLabels , packageRepo , ix , r , importData , from )
@@ -313,9 +319,10 @@ func infoToRules(repoRoot string, flags []Flag, ruleInfos []RuleInfo, configGrou
313319 // Generate rules for each component
314320 for _ , ruleInfo := range ruleInfos {
315321 r := rule .NewRule (ruleInfo .Kind , ruleInfo .Name )
316- r .SetAttr ("srcs" , ruleInfo .Srcs )
322+
323+ r .SetAttr ("srcs" , consolidateConfigurableList (ruleInfo .Srcs ))
317324 if ruleInfo .HiddenModules != nil {
318- r .SetAttr ("hidden_modules" , ruleInfo .HiddenModules )
325+ r .SetAttr ("hidden_modules" , consolidateConfigurableList ( ruleInfo .HiddenModules ) )
319326 }
320327 for k , v := range ruleInfo .Attrs {
321328 r .SetAttr (k , v )
@@ -695,3 +702,291 @@ func hasRuleInfo(ruleInfos []RuleInfo, kind string, name string) bool {
695702 }
696703 return false
697704}
705+
706+ // consolidateConfigurableList merges ConfigurableList items to avoid duplicates
707+ // It combines values under identical condition keys across multiple selects,
708+ // but splits selects to avoid ambiguous matches when the same value appears in multiple conditions.
709+ // Values in the plain list are removed from all select conditions to avoid duplication.
710+ // This preserves Bazel's select semantics where conditions must not be ambiguous.
711+ func consolidateConfigurableList (cl ConfigurableList [string ]) ConfigurableList [string ] {
712+ if len (cl ) == 0 {
713+ return cl
714+ }
715+
716+ // Separate plain values from selects
717+ var plainValues []string
718+ var selects []rule.SelectStringListValue
719+
720+ for _ , item := range cl {
721+ switch v := item .(type ) {
722+ case []string :
723+ plainValues = append (plainValues , v ... )
724+ case map [string ][]string :
725+ // Convert to SelectStringListValue
726+ selectVal := make (rule.SelectStringListValue )
727+ for k , vals := range v {
728+ selectVal [k ] = vals
729+ }
730+ selects = append (selects , selectVal )
731+ case rule.SelectStringListValue :
732+ selects = append (selects , v )
733+ }
734+ }
735+
736+ // Build result
737+ result := make (ConfigurableList [string ], 0 , len (selects )+ 1 )
738+
739+ // Deduplicate plain values
740+ plainValuesDedup := deduplicateStrings (plainValues )
741+
742+ // Create a set of plain values for fast lookup
743+ plainValuesSet := make (map [string ]bool , len (plainValuesDedup ))
744+ for _ , v := range plainValuesDedup {
745+ plainValuesSet [v ] = true
746+ }
747+
748+ // Add deduplicated plain values first
749+ if len (plainValuesDedup ) > 0 {
750+ result = append (result , plainValuesDedup )
751+ }
752+
753+ // Merge all selects into a single select first
754+ if len (selects ) > 0 {
755+ // Check if all selects have identical conditions (not just values)
756+ canMerge := true
757+ if len (selects ) > 1 {
758+ var firstConditions []string
759+ for i , sel := range selects {
760+ var conditions []string
761+ for condition := range sel {
762+ conditions = append (conditions , condition )
763+ }
764+ sort .Strings (conditions )
765+ if i == 0 {
766+ firstConditions = conditions
767+ } else {
768+ if len (conditions ) != len (firstConditions ) {
769+ canMerge = false
770+ break
771+ }
772+ for j , cond := range conditions {
773+ if cond != firstConditions [j ] {
774+ canMerge = false
775+ break
776+ }
777+ }
778+ if ! canMerge {
779+ break
780+ }
781+ }
782+ }
783+ }
784+
785+ if ! canMerge {
786+ // Cannot safely merge - keep as separate selects to avoid ambiguous matches
787+ for _ , sel := range selects {
788+ newSelect := make (rule.SelectStringListValue )
789+ for condition , vals := range sel {
790+ // Filter out values that are in plain list
791+ filtered := make ([]string , 0 , len (vals ))
792+ for _ , v := range vals {
793+ if ! plainValuesSet [v ] {
794+ filtered = append (filtered , v )
795+ }
796+ }
797+ // Deduplicate
798+ filtered = deduplicateStrings (filtered )
799+ newSelect [condition ] = filtered
800+ }
801+ // Ensure default is present
802+ if _ , exists := newSelect ["//conditions:default" ]; ! exists {
803+ newSelect ["//conditions:default" ] = []string {}
804+ }
805+ // Only add if there are non-default conditions with values
806+ hasNonDefaultValues := false
807+ for condition , vals := range newSelect {
808+ if condition != "//conditions:default" && len (vals ) > 0 {
809+ hasNonDefaultValues = true
810+ break
811+ }
812+ }
813+ if hasNonDefaultValues {
814+ result = append (result , newSelect )
815+ }
816+ }
817+ return result
818+ }
819+
820+ // Safe to merge - all selects have identical conditions
821+ mergedSelect := make (rule.SelectStringListValue )
822+
823+ for _ , sel := range selects {
824+ for condition , vals := range sel {
825+ mergedSelect [condition ] = append (mergedSelect [condition ], vals ... )
826+ }
827+ }
828+
829+ // Deduplicate values within each condition and remove values that are in plain list
830+ for condition , vals := range mergedSelect {
831+ deduped := deduplicateStrings (vals )
832+ filtered := make ([]string , 0 , len (deduped ))
833+ for _ , v := range deduped {
834+ if ! plainValuesSet [v ] {
835+ filtered = append (filtered , v )
836+ }
837+ }
838+ mergedSelect [condition ] = filtered
839+ }
840+
841+ // Remove empty conditions (but keep track of default)
842+ defaultVals , hasDefault := mergedSelect ["//conditions:default" ]
843+ for condition , vals := range mergedSelect {
844+ if len (vals ) == 0 && condition != "//conditions:default" {
845+ delete (mergedSelect , condition )
846+ }
847+ }
848+
849+ // Now split the select by shared values to avoid ambiguous matches
850+ // For each value, find all conditions that include it and create a separate select
851+ valueToConditions := make (map [string ][]string )
852+ for condition , vals := range mergedSelect {
853+ for _ , v := range vals {
854+ valueToConditions [v ] = append (valueToConditions [v ], condition )
855+ }
856+ }
857+
858+ // Group values by their condition sets (values with same conditions go together)
859+ type conditionSet struct {
860+ conditions []string
861+ values []string
862+ }
863+ conditionSets := make (map [string ]* conditionSet )
864+
865+ for value , conditions := range valueToConditions {
866+ sort .Strings (conditions )
867+ key := strings .Join (conditions , "|" )
868+ if cs , exists := conditionSets [key ]; exists {
869+ cs .values = append (cs .values , value )
870+ } else {
871+ conditionSets [key ] = & conditionSet {
872+ conditions : conditions ,
873+ values : []string {value },
874+ }
875+ }
876+ }
877+
878+ // Check if we need to split: only split if there are overlapping conditions
879+ // Overlapping means a condition appears in multiple condition sets
880+ conditionCounts := make (map [string ]int )
881+ for _ , cs := range conditionSets {
882+ for _ , cond := range cs .conditions {
883+ conditionCounts [cond ]++
884+ }
885+ }
886+
887+ hasOverlap := false
888+ for _ , count := range conditionCounts {
889+ if count > 1 {
890+ hasOverlap = true
891+ break
892+ }
893+ }
894+
895+ if hasOverlap {
896+ // Split: create a separate select for each condition set
897+ for _ , cs := range conditionSets {
898+ newSelect := make (rule.SelectStringListValue )
899+ for _ , condition := range cs .conditions {
900+ newSelect [condition ] = append ([]string {}, cs .values ... )
901+ }
902+ // Add empty default condition if not already present
903+ if _ , hasDefault := newSelect ["//conditions:default" ]; ! hasDefault {
904+ newSelect ["//conditions:default" ] = []string {}
905+ }
906+ result = append (result , newSelect )
907+ }
908+ } else {
909+ // No overlap: keep as single select (more compact)
910+ if len (mergedSelect ) > 0 {
911+ // Ensure default is present (use existing or add empty)
912+ if _ , exists := mergedSelect ["//conditions:default" ]; ! exists {
913+ if hasDefault {
914+ mergedSelect ["//conditions:default" ] = defaultVals
915+ } else {
916+ mergedSelect ["//conditions:default" ] = []string {}
917+ }
918+ }
919+ result = append (result , mergedSelect )
920+ }
921+ }
922+ }
923+
924+ return result
925+ }
926+
927+ // deduplicateStrings removes duplicate strings while preserving order
928+ func deduplicateStrings (slice []string ) []string {
929+ seen := make (map [string ]bool , len (slice ))
930+ result := make ([]string , 0 , len (slice ))
931+
932+ for _ , item := range slice {
933+ if ! seen [item ] {
934+ seen [item ] = true
935+ result = append (result , item )
936+ }
937+ }
938+
939+ return result
940+ }
941+
942+ // generatePathsModuleContentLines generates the content for an autogenerated Paths_ module as an array of lines
943+ func generatePathsModuleContentLines (moduleName string , version string ) []string {
944+ return []string {
945+ fmt .Sprintf ("module %s where" , moduleName ),
946+ " " ,
947+ "import Data.Version (Version, makeVersion)" ,
948+ " " ,
949+ "version :: Version" ,
950+ fmt .Sprintf ("version = makeVersion [%s]" , convertVersionToList (version )),
951+ }
952+ }
953+
954+ // convertVersionToList converts a version string like "1.0.2.0" to "1, 0, 2, 0"
955+ func convertVersionToList (version string ) string {
956+ // Simple conversion: replace dots with ", "
957+ result := ""
958+ for i , c := range version {
959+ if c == '.' {
960+ result += ", "
961+ } else {
962+ result += string (c )
963+ }
964+ if i == len (version )- 1 {
965+ break
966+ }
967+ }
968+ return result
969+ }
970+
971+ // prependToConfigurableList prepends a value to a ConfigurableList
972+ func prependToConfigurableList (list ConfigurableList [string ], value string ) ConfigurableList [string ] {
973+ newList := make (ConfigurableList [string ], len (list ))
974+ for i , item := range list {
975+ switch v := item .(type ) {
976+ case []string :
977+ // For plain slice, prepend value
978+ newList [i ] = append ([]string {value }, v ... )
979+ case map [string ][]string :
980+ // For select/map, prepend to each conditional value
981+ newConditionals := make (map [string ][]string )
982+ for k , vals := range v {
983+ newConditionals [k ] = append ([]string {value }, vals ... )
984+ }
985+ newList [i ] = newConditionals
986+ default :
987+ // Keep as is if unexpected type
988+ newList [i ] = item
989+ }
990+ }
991+ return newList
992+ }
0 commit comments