Skip to content

Commit

Permalink
feat: implement support for PascalCase module names with snake_case d…
Browse files Browse the repository at this point in the history
…irectory names (#534)

This PR introduces an optional Gazelle directive to convert module names
from snake_case to PascalCase for consistency with platform idioms.
Currently the module name is the same as the target name, derived
typically from the directory name.

Secondly, the current heuristic for detecting whether a directory is a
swift_test or swift_library / swift_binary checks for case sensitive,
"Test" or "Tests" directory names or "Test.swift" / "Tests.swift" file
names. This breaks if the directory name is in snake_case.

To address this issue I just made the checks case insensitive. I also
unified the DirSuffixes and fileSuffixes types into a PathSuffixes type
and added tests for both files and paths with both casings.
  • Loading branch information
AttilaTheFun authored Aug 29, 2023
1 parent eaed5f0 commit f354e5b
Show file tree
Hide file tree
Showing 18 changed files with 145 additions and 71 deletions.
4 changes: 2 additions & 2 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# To update these lines, execute
# `bazel run @rules_bazel_integration_test//tools:update_deleted_packages`
build --deleted_packages=bzlmod/workspace,bzlmod/workspace/Sources/MyExecutable,bzlmod/workspace/Sources/MyLibrary,bzlmod/workspace/Tests/MyLibraryTests,examples/firebase_example,examples/firebase_example/abtesting,examples/firebase_example/abtesting/SharedApp,examples/firebase_example/analytics/AnalyticsExample,examples/firebase_example/appdistribution,examples/firebase_example/appdistribution/AppDistributionExample,examples/firebase_example/appdistribution/AppDistributionTests,examples/firebase_example/crashlytics,examples/grpc_example,examples/grpc_example/protos,examples/grpc_example/protos/echoservice,examples/grpc_example/protos/echoservice/messages,examples/grpc_example/sources,examples/grpc_example/sources/client,examples/grpc_example/sources/server,examples/grpc_example/sources/test,examples/http_archive_ext_deps,examples/http_archive_ext_deps/Sources/MyDequeModule,examples/http_archive_ext_deps/Sources/PrintStuff,examples/http_archive_ext_deps/Tests/MyDequeModuleTests,examples/http_archive_ext_deps/third_party,examples/interesting_deps,examples/ios_sim,examples/ios_sim/Sources/Foo,examples/ios_sim/Tests/FooTests,examples/ios_sim/third-party/swift-cmark,examples/nimble_example,examples/nimble_example/Sources/NimbleExample,examples/objc_code,examples/phone_number_kit,examples/phone_number_kit/Tests/PhoneNumberKitTests,examples/pkg_manifest_minimal,examples/pkg_manifest_minimal/Sources/MyExecutable,examples/pkg_manifest_minimal/Sources/MyLibrary,examples/pkg_manifest_minimal/Tests/MyLibraryTests,examples/pkg_manifest_minimal/third_party,examples/resources_example,examples/resources_example/Sources/MyApp,examples/resources_example/Tests/MyAppTests,examples/resources_example/third_party,examples/snapkit_example,examples/soto_example,examples/soto_example/Tests/SotoTests,examples/stripe_example,examples/stripe_example/PaymentSheet/PaymentSheetExample,examples/stripe_example/PaymentSheet/PaymentSheetUITest,examples/tca_example,examples/tca_example/SwiftUICaseStudies,examples/tca_example/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads,examples/tca_example/SwiftUICaseStudies/Internal,examples/tca_example/SwiftUICaseStudiesTests,examples/vapor_example,examples/vapor_example/Sources/App,examples/vapor_example/Sources/Run,examples/vapor_example/Tests/AppTests,examples/vapor_example/swift,examples/xcmetrics_example
query --deleted_packages=bzlmod/workspace,bzlmod/workspace/Sources/MyExecutable,bzlmod/workspace/Sources/MyLibrary,bzlmod/workspace/Tests/MyLibraryTests,examples/firebase_example,examples/firebase_example/abtesting,examples/firebase_example/abtesting/SharedApp,examples/firebase_example/analytics/AnalyticsExample,examples/firebase_example/appdistribution,examples/firebase_example/appdistribution/AppDistributionExample,examples/firebase_example/appdistribution/AppDistributionTests,examples/firebase_example/crashlytics,examples/grpc_example,examples/grpc_example/protos,examples/grpc_example/protos/echoservice,examples/grpc_example/protos/echoservice/messages,examples/grpc_example/sources,examples/grpc_example/sources/client,examples/grpc_example/sources/server,examples/grpc_example/sources/test,examples/http_archive_ext_deps,examples/http_archive_ext_deps/Sources/MyDequeModule,examples/http_archive_ext_deps/Sources/PrintStuff,examples/http_archive_ext_deps/Tests/MyDequeModuleTests,examples/http_archive_ext_deps/third_party,examples/interesting_deps,examples/ios_sim,examples/ios_sim/Sources/Foo,examples/ios_sim/Tests/FooTests,examples/ios_sim/third-party/swift-cmark,examples/nimble_example,examples/nimble_example/Sources/NimbleExample,examples/objc_code,examples/phone_number_kit,examples/phone_number_kit/Tests/PhoneNumberKitTests,examples/pkg_manifest_minimal,examples/pkg_manifest_minimal/Sources/MyExecutable,examples/pkg_manifest_minimal/Sources/MyLibrary,examples/pkg_manifest_minimal/Tests/MyLibraryTests,examples/pkg_manifest_minimal/third_party,examples/resources_example,examples/resources_example/Sources/MyApp,examples/resources_example/Tests/MyAppTests,examples/resources_example/third_party,examples/snapkit_example,examples/soto_example,examples/soto_example/Tests/SotoTests,examples/stripe_example,examples/stripe_example/PaymentSheet/PaymentSheetExample,examples/stripe_example/PaymentSheet/PaymentSheetUITest,examples/tca_example,examples/tca_example/SwiftUICaseStudies,examples/tca_example/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads,examples/tca_example/SwiftUICaseStudies/Internal,examples/tca_example/SwiftUICaseStudiesTests,examples/vapor_example,examples/vapor_example/Sources/App,examples/vapor_example/Sources/Run,examples/vapor_example/Tests/AppTests,examples/vapor_example/swift,examples/xcmetrics_example
build --deleted_packages=bzlmod/workspace,bzlmod/workspace/Sources/MyExecutable,bzlmod/workspace/Sources/MyLibrary,bzlmod/workspace/Tests/MyLibraryTests,examples/firebase_example,examples/firebase_example/abtesting,examples/firebase_example/abtesting/SharedApp,examples/firebase_example/analytics/AnalyticsExample,examples/firebase_example/appdistribution,examples/firebase_example/appdistribution/AppDistributionExample,examples/firebase_example/appdistribution/AppDistributionTests,examples/firebase_example/crashlytics,examples/grpc_example,examples/grpc_example/protos/echoservice,examples/grpc_example/protos/echoservice/messages,examples/grpc_example/sources,examples/grpc_example/sources/client,examples/grpc_example/sources/server,examples/grpc_example/sources/test,examples/http_archive_ext_deps,examples/http_archive_ext_deps/Sources/MyDequeModule,examples/http_archive_ext_deps/Sources/PrintStuff,examples/http_archive_ext_deps/Tests/MyDequeModuleTests,examples/http_archive_ext_deps/third_party,examples/interesting_deps,examples/ios_sim,examples/ios_sim/Sources/Foo,examples/ios_sim/Tests/FooTests,examples/ios_sim/third-party/swift-cmark,examples/nimble_example,examples/nimble_example/Sources/NimbleExample,examples/objc_code,examples/phone_number_kit,examples/phone_number_kit/Tests/PhoneNumberKitTests,examples/pkg_manifest_minimal,examples/pkg_manifest_minimal/Sources/MyExecutable,examples/pkg_manifest_minimal/Sources/MyLibrary,examples/pkg_manifest_minimal/Tests/MyLibraryTests,examples/pkg_manifest_minimal/third_party,examples/resources_example,examples/resources_example/Sources/MyApp,examples/resources_example/Tests/MyAppTests,examples/resources_example/third_party,examples/snapkit_example,examples/soto_example,examples/soto_example/Tests/SotoTests,examples/stripe_example,examples/stripe_example/PaymentSheet/PaymentSheetExample,examples/stripe_example/PaymentSheet/PaymentSheetUITest,examples/tca_example,examples/tca_example/SwiftUICaseStudies,examples/tca_example/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads,examples/tca_example/SwiftUICaseStudies/Internal,examples/tca_example/SwiftUICaseStudiesTests,examples/vapor_example,examples/vapor_example/Sources/App,examples/vapor_example/Sources/Run,examples/vapor_example/Tests/AppTests,examples/vapor_example/swift,examples/xcmetrics_example
query --deleted_packages=bzlmod/workspace,bzlmod/workspace/Sources/MyExecutable,bzlmod/workspace/Sources/MyLibrary,bzlmod/workspace/Tests/MyLibraryTests,examples/firebase_example,examples/firebase_example/abtesting,examples/firebase_example/abtesting/SharedApp,examples/firebase_example/analytics/AnalyticsExample,examples/firebase_example/appdistribution,examples/firebase_example/appdistribution/AppDistributionExample,examples/firebase_example/appdistribution/AppDistributionTests,examples/firebase_example/crashlytics,examples/grpc_example,examples/grpc_example/protos/echoservice,examples/grpc_example/protos/echoservice/messages,examples/grpc_example/sources,examples/grpc_example/sources/client,examples/grpc_example/sources/server,examples/grpc_example/sources/test,examples/http_archive_ext_deps,examples/http_archive_ext_deps/Sources/MyDequeModule,examples/http_archive_ext_deps/Sources/PrintStuff,examples/http_archive_ext_deps/Tests/MyDequeModuleTests,examples/http_archive_ext_deps/third_party,examples/interesting_deps,examples/ios_sim,examples/ios_sim/Sources/Foo,examples/ios_sim/Tests/FooTests,examples/ios_sim/third-party/swift-cmark,examples/nimble_example,examples/nimble_example/Sources/NimbleExample,examples/objc_code,examples/phone_number_kit,examples/phone_number_kit/Tests/PhoneNumberKitTests,examples/pkg_manifest_minimal,examples/pkg_manifest_minimal/Sources/MyExecutable,examples/pkg_manifest_minimal/Sources/MyLibrary,examples/pkg_manifest_minimal/Tests/MyLibraryTests,examples/pkg_manifest_minimal/third_party,examples/resources_example,examples/resources_example/Sources/MyApp,examples/resources_example/Tests/MyAppTests,examples/resources_example/third_party,examples/snapkit_example,examples/soto_example,examples/soto_example/Tests/SotoTests,examples/stripe_example,examples/stripe_example/PaymentSheet/PaymentSheetExample,examples/stripe_example/PaymentSheet/PaymentSheetUITest,examples/tca_example,examples/tca_example/SwiftUICaseStudies,examples/tca_example/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads,examples/tca_example/SwiftUICaseStudies/Internal,examples/tca_example/SwiftUICaseStudiesTests,examples/vapor_example,examples/vapor_example/Sources/App,examples/vapor_example/Sources/Run,examples/vapor_example/Tests/AppTests,examples/vapor_example/swift,examples/xcmetrics_example

# Import Shared settings
import %workspace%/shared.bazelrc
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use_repo(
"com_github_stretchr_testify",
"in_gopkg_yaml_v3",
"org_golang_x_exp",
"org_golang_x_text",
)

# MARK: - Dev Dependencies
Expand Down
1 change: 1 addition & 0 deletions examples/grpc_example/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ tidy(

# Ignore the Swift build folder
# gazelle:exclude .build
# gazelle:swift_module_naming_convention pascal_case

gazelle_binary(
name = "gazelle_bin",
Expand Down
Empty file.
2 changes: 1 addition & 1 deletion examples/grpc_example/sources/client/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_binary")
swift_binary(
name = "client",
srcs = ["client_main.swift"],
module_name = "client",
module_name = "Client",
visibility = ["//visibility:public"],
deps = [
"//protos/echoservice:echoservice_client_swift_grpc",
Expand Down
2 changes: 1 addition & 1 deletion examples/grpc_example/sources/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_binary")
swift_binary(
name = "server",
srcs = ["server_main.swift"],
module_name = "server",
module_name = "Server",
visibility = ["//visibility:public"],
deps = [
"//protos/echoservice:echoservice_server_swift_grpc",
Expand Down
3 changes: 1 addition & 2 deletions examples/grpc_example/sources/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ swift_test(
"client_unit_test.swift",
"main.swift",
],
module_name = "test",
visibility = ["//visibility:public"],
module_name = "Test",
deps = [
"//protos/echoservice:echoservice_client_swift_grpc",
"//protos/echoservice:echoservice_server_swift_grpc",
Expand Down
2 changes: 2 additions & 0 deletions gazelle/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ go_library(
"@bazel_gazelle//resolve:go_default_library",
"@bazel_gazelle//rule:go_default_library",
"@org_golang_x_exp//slices:go_default_library",
"@org_golang_x_text//cases:go_default_library",
"@org_golang_x_text//language:go_default_library",
],
)

Expand Down
12 changes: 11 additions & 1 deletion gazelle/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,14 @@ func (sl *swiftLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error {

// Directives

const moduleNamingConventionDirective = "swift_module_naming_convention"
const defaultModuleNameDirective = "swift_default_module_name"

func (*swiftLang) KnownDirectives() []string {
return []string{defaultModuleNameDirective}
return []string{
moduleNamingConventionDirective,
defaultModuleNameDirective,
}
}

func (*swiftLang) Configure(c *config.Config, rel string, f *rule.File) {
Expand All @@ -138,6 +142,12 @@ func (*swiftLang) Configure(c *config.Config, rel string, f *rule.File) {
sc := swiftcfg.GetSwiftConfig(c)
for _, d := range f.Directives {
switch d.Key {
case moduleNamingConventionDirective:
if d.Value == swiftcfg.PascalCaseModuleNamingConvention {
sc.ModuleNamingConvention = swiftcfg.PascalCaseModuleNamingConvention
} else {
sc.ModuleNamingConvention = swiftcfg.MatchCaseModuleNamingConvention
}
case defaultModuleNameDirective:
sc.DefaultModuleNames[rel] = d.Value
}
Expand Down
58 changes: 36 additions & 22 deletions gazelle/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/cgrindel/rules_swift_package_manager/gazelle/internal/swift"
"github.com/cgrindel/rules_swift_package_manager/gazelle/internal/swiftcfg"
"golang.org/x/exp/slices"
"golang.org/x/text/cases"
lang "golang.org/x/text/language"
)

func (l *swiftLang) GenerateRules(args language.GenerateArgs) language.GenerateResult {
Expand Down Expand Up @@ -62,44 +64,56 @@ func genRulesFromSrcFiles(sc *swiftcfg.SwiftConfig, args language.GenerateArgs)
sort.Strings(srcs)

// Generate the rules from sources:
defaultModuleName := defaultModuleName(args)
rules = swift.RulesFromSrcs(args, srcs, defaultModuleName)
defaultName, defaultModuleName := defaultNameAndModuleName(args)
rules = swift.RulesFromSrcs(args, srcs, defaultName, defaultModuleName)
result.Gen = append(result.Gen, rules...)
result.Imports = swift.Imports(result.Gen)
result.Empty = generateEmpty(args, srcs)

return result
}

func defaultModuleName(args language.GenerateArgs) string {
// Order of names to use
// 1. Value specified via directive.
// 2. Directory name.
// 3. Repository name.
// 4. "DefaultModule"
func defaultNameAndModuleName(args language.GenerateArgs) (string, string) {

// Check for a value configured via directive
// If the name is specified by a directive, short cirucit and return that:
sc := swiftcfg.GetSwiftConfig(args.Config)
var defaultModuleName string
var ok bool
if defaultModuleName, ok = sc.DefaultModuleNames[args.Rel]; ok {
return defaultModuleName
if defaultModuleName, ok := sc.DefaultModuleNames[args.Rel]; ok {
return defaultModuleName, defaultModuleName
}

// Otherwise, derive the name from the:
// 1. Directory name.
// 2. Repository name.
// 3. "DefaultModule"
var defaultName string
if args.Rel == "" {
defaultModuleName = filepath.Base(args.Config.WorkDir)
defaultName = filepath.Base(args.Config.WorkDir)
} else {
defaultModuleName = filepath.Base(args.Rel)
defaultName = filepath.Base(args.Rel)
}
if ext := filepath.Ext(defaultModuleName); ext != "" {
defaultModuleName = strings.TrimSuffix(defaultModuleName, ext)
if ext := filepath.Ext(defaultName); ext != "" {
defaultName = strings.TrimSuffix(defaultName, ext)
}
if defaultModuleName == "." || defaultModuleName == "" {
defaultModuleName = args.Config.RepoName
if defaultName == "." || defaultName == "" {
defaultName = args.Config.RepoName
}
if defaultModuleName == "" {
defaultModuleName = "DefaultModule"
if defaultName == "" {
defaultName = "DefaultModule"
}
return defaultModuleName

// If configured to use PascalCase for module names, convert to that naming convention:
defaultModuleName := defaultName
if sc.ModuleNamingConvention == swiftcfg.PascalCaseModuleNamingConvention {
moduleNameComponents := strings.Split(defaultModuleName, "_")
caser := cases.Title(lang.English)
pascalCaseModuleName := ""
for _, component := range moduleNameComponents {
pascalCaseModuleName += caser.String(component)
}
defaultModuleName = pascalCaseModuleName
}

return defaultName, defaultModuleName
}

// Look for any rules in the existing BUILD file that do not reference one of the source files. If
Expand Down
13 changes: 8 additions & 5 deletions gazelle/internal/swift/rules_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,45 @@ import (
// Rule Creation

func rulesForLibraryModule(
defaultName string,
defaultModuleName string,
srcs []string,
swiftImports []string,
shouldSetVis bool,
buildFile *rule.File,
) []*rule.Rule {
name, moduleName := ruleNameAndModuleName(buildFile, LibraryRuleKind, defaultModuleName)
name, moduleName := ruleNameAndModuleName(buildFile, LibraryRuleKind, defaultName, defaultModuleName)
r := rule.NewRule(LibraryRuleKind, name)
setCommonSwiftAttrs(r, moduleName, srcs, swiftImports)
setVisibilityAttr(r, shouldSetVis, []string{"//visibility:public"})
return []*rule.Rule{r}
}

func rulesForBinaryModule(
defaultName string,
defaultModuleName string,
srcs []string,
swiftImports []string,
shouldSetVis bool,
buildFile *rule.File,
) []*rule.Rule {
name, moduleName := ruleNameAndModuleName(buildFile, BinaryRuleKind, defaultModuleName)
name, moduleName := ruleNameAndModuleName(buildFile, BinaryRuleKind, defaultName, defaultModuleName)
r := rule.NewRule(BinaryRuleKind, name)
setCommonSwiftAttrs(r, moduleName, srcs, swiftImports)
setVisibilityAttr(r, shouldSetVis, []string{"//visibility:public"})
return []*rule.Rule{r}
}

func rulesForTestModule(
defaultName string,
defaultModuleName string,
srcs []string,
swiftImports []string,
shouldSetVis bool,
buildFile *rule.File,
) []*rule.Rule {
// Detect the type of rule that should be used to build the Swift sources.
r := buildRuleForTestSrcs(buildFile, defaultModuleName)
r := buildRuleForTestSrcs(buildFile, defaultName, defaultModuleName)
setCommonSwiftAttrs(r, defaultModuleName, srcs, swiftImports)
return []*rule.Rule{r}
}
Expand Down Expand Up @@ -79,7 +82,7 @@ func setVisibilityAttr(r *rule.Rule, shouldSetVis bool, visibility []string) {
// Name and Module Name

// Determine the rule name and module name from existing rules
func ruleNameAndModuleName(buildFile *rule.File, kind, defaultModuleName string) (string, string) {
func ruleNameAndModuleName(buildFile *rule.File, kind, defaultName, defaultModuleName string) (string, string) {
var existingRules []*rule.Rule
if buildFile != nil {
existingRules = findRulesByKind(buildFile.Rules, kind)
Expand All @@ -91,7 +94,7 @@ func ruleNameAndModuleName(buildFile *rule.File, kind, defaultModuleName string)
name = first.Name()
moduleName = first.AttrString(ModuleNameAttrName)
} else {
name = defaultModuleName
name = defaultName
moduleName = defaultModuleName
}
return name, moduleName
Expand Down
19 changes: 12 additions & 7 deletions gazelle/internal/swift/rules_from_srcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
func RulesFromSrcs(
args language.GenerateArgs,
srcs []string,
defaultName string,
defaultModuleName string,
) []*rule.Rule {
fileInfos := swiftpkg.NewSwiftFileInfosFromRelPaths(args.Dir, srcs)
Expand All @@ -23,11 +24,11 @@ func RulesFromSrcs(
var rules []*rule.Rule
switch moduleType {
case LibraryModuleType:
rules = rulesForLibraryModule(defaultModuleName, srcs, swiftImports, shouldSetVis, args.File)
rules = rulesForLibraryModule(defaultName, defaultModuleName, srcs, swiftImports, shouldSetVis, args.File)
case BinaryModuleType:
rules = rulesForBinaryModule(defaultModuleName, srcs, swiftImports, shouldSetVis, args.File)
rules = rulesForBinaryModule(defaultName, defaultModuleName, srcs, swiftImports, shouldSetVis, args.File)
case TestModuleType:
rules = rulesForTestModule(defaultModuleName, srcs, swiftImports, shouldSetVis, args.File)
rules = rulesForTestModule(defaultName, defaultModuleName, srcs, swiftImports, shouldSetVis, args.File)
}
return rules
}
Expand Down Expand Up @@ -61,10 +62,14 @@ func collectSwiftInfo(fileInfos []*swiftpkg.SwiftFileInfo) ([]string, ModuleType
}
}

// Adjust the rule kind, if necessary
// Check for test files first. On Linux, a main.swift is necessary for swift_test rules.
// GUI applications can use the @main directive. So, we need to see if the module contains any
// of the GUI related modules. If no GUI modules and it has a main, then create a swift_binary.
// Adjust the rule kind, if necessary.
// Check if this is a test module first. On Linux, a main.swift is necessary for swift_test rules.
// rules_swift_package_manager does not currently support generating rules_apple targets.
// However, applications using Apple's UI frameworks can use the @main directive.
// To build these with rules_apple, we must first compile a swift_library target,
// and then pass this as a dependency to the application or extension target.
// So, we need to see if the module contains a main function and imports any of the GUI related modules.
// If it does not import any GUI modules and it has a main, then create a swift_binary.
if hasTestFiles {
moduleType = TestModuleType
} else if hasMain && !importsGUIModules {
Expand Down
Loading

0 comments on commit f354e5b

Please sign in to comment.