From b05bee22c0f8b238d9c59b100733b5ceb6d7245e Mon Sep 17 00:00:00 2001 From: Peter Freiberg <109841928+sabbaticas@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:10:01 +1100 Subject: [PATCH 1/2] Detect CocoaPods and Carthage dependency files Podfile and Podfile.lock are tagged dependencies and CocoaPods. Cartfile, Cartfile.private and Cartfile.resolved are tagged dependencies and Carthage. An iOS or macOS project using either tool previously looked like it declared no dependencies. Podfile now reports as Ruby, which is how CocoaPods loads it. The three Carthage files report as a new Cartfile filetype; the two with an extension are mapped by basename so that .private and .resolved are not claimed in general. Neither tool's files carry a language tag, since both serve Swift and Objective-C alike. The changes notes record the sources each convention was verified against, and the docs site counts are updated to 123 tags and 88 filetypes. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 +- changes/2026-10-cocoapods-and-carthage.md | 123 +++++++++++++++++++ changes/2026-10-objective-cpp-and-swiftpm.md | 6 + docs/api.md | 6 +- docs/cli.md | 6 +- src/panopticas/constants.py | 14 +++ src/tests/Cartfile | 1 + src/tests/Cartfile.private | 1 + src/tests/Cartfile.resolved | 2 + src/tests/Podfile | 6 + src/tests/Podfile.lock | 16 +++ tests/test_panopticas.py | 50 +++++++- 12 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 changes/2026-10-cocoapods-and-carthage.md create mode 100644 src/tests/Cartfile create mode 100644 src/tests/Cartfile.private create mode 100644 src/tests/Cartfile.resolved create mode 100644 src/tests/Podfile create mode 100644 src/tests/Podfile.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 48161a4..bbdd1d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,13 @@ The format of this changelog is based on [Keep a Changelog](https://keepachangel - `settings.gradle` and `settings.gradle.kts` are tagged `gradle` and `build`. They declare project structure rather than dependencies, so they do not carry `dependencies`. - `.mm` now reports as `Objective-C++`, a new language. It was missing from the extension table and reported `Unknown`. - Swift Package Manager files are recognised. `Package.swift` is tagged `build`, `dependencies`, `Swift` and `SwiftPM`; its lock file `Package.resolved` is tagged `dependencies`, `Swift` and `SwiftPM` and reports as `JSON` instead of `Unknown`. A Swift package previously looked like it declared no dependencies. `Swift` and `SwiftPM` are new tags. + - CocoaPods files are recognised. `Podfile` and `Podfile.lock` are tagged `dependencies` and `CocoaPods`, and `Podfile` reports as `Ruby` instead of `Unknown`. + - Carthage files are recognised. `Cartfile`, `Cartfile.private` and `Cartfile.resolved` are tagged `dependencies` and `Carthage`, and report as a new `Cartfile` filetype instead of `Unknown`. `CocoaPods` and `Carthage` are new tags. Neither tool's files carry a language tag, since both serve Swift and Objective-C alike. All of these are new tags on previously untagged files, so kospex is compatible either way; a re-sync is needed to pick them up. ### Changed - - The tag vocabulary grew from 119 to 121 tags, the filetype vocabulary from 86 to 87 and the language vocabulary from 32 to 33. + - The tag vocabulary grew from 119 to 123 tags, the filetype vocabulary from 86 to 88 and the language vocabulary from 32 to 33. - The Click floor moved from `>=8.3.1` to `>=8.3.3`, so an install can no longer resolve to a vulnerable Click release. - The package author email is now `peter@kospex.io`. diff --git a/changes/2026-10-cocoapods-and-carthage.md b/changes/2026-10-cocoapods-and-carthage.md new file mode 100644 index 0000000..f0f1cea --- /dev/null +++ b/changes/2026-10-cocoapods-and-carthage.md @@ -0,0 +1,123 @@ +# CocoaPods and Carthage Detection + +## Summary + +Neither dependency manager's files were recognised. Only `Podfile.lock` had a +filetype, and only through the generic `.lock` extension: + + Podfile [] 'Unknown' + Podfile.lock [] 'Lock' + Cartfile [] 'Unknown' + Cartfile.private [] 'Unknown' + Cartfile.resolved [] 'Unknown' + +An iOS or macOS project using either tool therefore looked like it declared no +dependencies. This completes the Apple-platform dependency managers begun with +Swift Package Manager in #38. + +## Changes + +Five entries in `METADATA_RULES["exact_filename_rules"]`: + + "podfile": ["dependencies", "CocoaPods"] + "podfile.lock": ["dependencies", "CocoaPods"] + "cartfile": ["dependencies", "Carthage"] + "cartfile.private": ["dependencies", "Carthage"] + "cartfile.resolved": ["dependencies", "Carthage"] + +Two extensionless entries in `EXT_FILETYPES`: + + "podfile": "Ruby" + "cartfile": "Cartfile" + +Two entries in `LANGUAGE_BY_BASENAME`, and `Cartfile` added to +`NON_LANGUAGE_FILETYPES`: + + "cartfile.private": "Cartfile" + "cartfile.resolved": "Cartfile" + +Result: + + Podfile ['dependencies', 'CocoaPods'] 'Ruby' + Podfile.lock ['dependencies', 'CocoaPods'] 'Lock' + Cartfile ['dependencies', 'Carthage'] 'Cartfile' + Cartfile.private ['dependencies', 'Carthage'] 'Cartfile' + Cartfile.resolved ['dependencies', 'Carthage'] 'Cartfile' + +## Sources + +| Convention | Source | +|---|---| +| The file is named `Podfile` and describes target dependencies | [The Podfile](https://guides.cocoapods.org/using/the-podfile.html) — "The file should simply be named `Podfile`." | +| `Podfile.lock` records the installed version of each pod and is kept in version control | [Using CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) | +| An extensionless `Podfile` is loaded as Ruby | [`podfile.rb` in CocoaPods/Core](https://github.com/CocoaPods/Core/blob/master/lib/cocoapods-core/podfile.rb) — `from_file` sends `''`, `.podfile` and `.rb` to `from_ruby` | +| `Cartfile`, `Cartfile.private`, `Cartfile.resolved` and the OGDL-subset syntax | [Carthage Artifacts](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md) | + +## Background: what Carthage is + +Recorded because it explains the tagging choices and took some checking. +Verified on 2026-10-09 against the sources named in each row. + +| Fact | Detail | Source | +|---|---|---| +| Apple platforms only | Describes itself as "a simple, decentralized dependency manager for Cocoa" | [Repository description](https://github.com/Carthage/Carthage) | +| Platforms | macOS, iOS, tvOS and watchOS | [README](https://github.com/Carthage/Carthage/blob/master/README.md) | +| visionOS | Supported in code — the SDK list includes visionOS and its simulator — but not mentioned in the README | [`Source/XCDBLD/SDK.swift`](https://github.com/Carthage/Carthage/blob/master/Source/XCDBLD/SDK.swift) | +| Languages | "frameworks written in Swift or Objective-C" | README | +| No central registry | "There is no central list of projects" | README | +| Linux and server-side Swift | Not stated either way. Unsupported by inference only: Carthage builds with Xcode's tools | — | +| Maintenance | Not archived; latest release is 0.40.0, September 2024 | [Releases](https://github.com/Carthage/Carthage/releases) | + +**Apple does not document Carthage.** It is a community project. A search of +developer.apple.com finds only forum threads and passing mentions in two WWDC +sessions; the +[WWDC22 Xcode Cloud session](https://developer.apple.com/videos/play/wwdc2022/110375/) +refers to CocoaPods and Carthage as third-party dependency managers needing +custom build scripts. Swift Package Manager is the only dependency manager +Apple documents as its own. There is therefore no Apple source to verify a +Carthage convention against — Carthage's own repository is the authority. + +Carthage, CocoaPods and Swift Package Manager all serve the same ecosystem. +Swift Package Manager is the default for new projects, so `Cartfile` and +`Podfile` are most often found in older codebases. + +## Design notes + +- **No language tag.** Both tools manage dependencies for Swift and + Objective-C projects alike, so a `Swift` tag would be wrong as often as + right. `Package.swift` carries `Swift` because the manifest itself is Swift. +- **No `build` tag.** These files declare and pin dependencies; they do not + describe a build. This matches `package.json` and `uv.lock`. +- **`Podfile` is `Ruby`.** The CocoaPods guides do not name the language, but + the loader evaluates an extensionless `Podfile` as Ruby. This follows the + existing `Steepfile` and `.rufo` mappings. A Podfile now counts towards a + repository's Ruby total. +- **`Cartfile` gets its own filetype.** Carthage documents the syntax as a + restricted subset of OGDL, which panopticas has no name for, so the file + takes its own name as the other tool-specific formats do. The Carthage + documentation does not name a syntax for `Cartfile.resolved`; it is given + the same filetype as the file it is generated from. +- **`Cartfile.private` and `Cartfile.resolved` are mapped by basename.** + `.private` and `.resolved` are not mapped as extensions — `Package.resolved` + is JSON, and `.private` means nothing in general. Tests guard that other + files with those extensions are not claimed. +- **`Podfile.lock` keeps the `Lock` filetype** it already had from its + extension, like `uv.lock`. +- `CocoaPods` and `Carthage` are new tags and `Cartfile` a new filetype. All + additive, so kospex is unaffected until a re-sync. + +## Not included + +- `*.podspec` and `*.podspec.json`, which describe a library published as a + pod rather than a project's dependencies. +- CocoaPods' alternative Podfile names (`CocoaPods.podfile`, + `CocoaPods.podfile.yaml`, `Podfile.rb`). The loader accepts them, but the + guides document only `Podfile`. +- The `Pods/` and `Carthage/` directories. + +## Tests + +`tests/test_panopticas.py` covers the filetype and tags for all five files, a +subdirectory path, and guards against look-alike names. Fixtures in +`src/tests/`: `Podfile`, `Podfile.lock`, `Cartfile`, `Cartfile.private` and +`Cartfile.resolved`. diff --git a/changes/2026-10-objective-cpp-and-swiftpm.md b/changes/2026-10-objective-cpp-and-swiftpm.md index 20e80fb..edc8a36 100644 --- a/changes/2026-10-objective-cpp-and-swiftpm.md +++ b/changes/2026-10-objective-cpp-and-swiftpm.md @@ -47,6 +47,12 @@ Result: project `Package.resolved` lives inside the project bundle, under `.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/`, and is tagged there too. +- **`SwiftPM` is the tool's own short name**, not a panopticas convention. + The official documentation is titled + [Package Manager (SwiftPM)](https://docs.swift.org/latest/documentation/packagemanagerdocs/) + — set as the display name in + [`Documentation.md`](https://github.com/swiftlang/swift-package-manager/blob/main/Sources/PackageManagerDocs/Documentation.docc/Documentation.md) + — and the project README refers to "SwiftPM's bug tracker". - **`Swift` and `SwiftPM` are new tags** and `Objective-C++` a new language name. `Swift` existed as a language but had never been used as a tag. All are additions, so kospex is unaffected until a re-sync. diff --git a/docs/api.md b/docs/api.md index c0fa107..ff16f66 100644 --- a/docs/api.md +++ b/docs/api.md @@ -53,7 +53,7 @@ EXT_FILETYPES["dockerfile"] # "Dockerfile" (special case) `EXT_FILETYPES` mixes two kinds of value: things that are programming languages (`Python`, `Go`) and things that are file types but not languages (`PNG`, `Gitignore`, `Lock`). These two collections classify every value in the table — -33 languages and 54 non-languages. `get_languages()` and `get_filetypes()` read +33 languages and 55 non-languages. `get_languages()` and `get_filetypes()` read from them. ```python @@ -369,7 +369,7 @@ Return every tag panopticas can assign to a file. from panopticas.core import get_tags get_tags() -# Returns: ['.NET', 'agent', 'Agents', 'AI', 'Aider', ..., 'yarn'] (121 tags) +# Returns: ['.NET', 'agent', 'Agents', 'AI', 'Aider', ..., 'yarn'] (123 tags) ``` **Returns:** Sorted list of tag strings, case-insensitively ordered @@ -386,7 +386,7 @@ Return every file type panopticas recognises, languages or not. from panopticas.core import get_filetypes get_filetypes() -# Returns: ['Apache JMeter', 'ASP.NET', ..., 'ZIP'] (87 file types) +# Returns: ['Apache JMeter', 'ASP.NET', ..., 'ZIP'] (88 file types) ``` **Returns:** Sorted list of file type strings diff --git a/docs/cli.md b/docs/cli.md index b55c86b..1dd545c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -382,7 +382,7 @@ Amazon Q Apache ASP.NET Augment Azure DevOps binary Biome Bitbucket build Buildkite ... -121 tags +123 tags ``` Values are sorted case-insensitively, so `binary` sorts near `Bitbucket` @@ -395,9 +395,9 @@ count. | Command | Shape | |---|---| -| `tags` | `{"tags": [...], "count": 121}` | +| `tags` | `{"tags": [...], "count": 123}` | | `languages` | `{"languages": [...], "count": 33}` | -| `filetypes` | `{"filetypes": [...], "count": 87}` | +| `filetypes` | `{"filetypes": [...], "count": 88}` | ```console $ panopticas languages --json | jq -r '.count' diff --git a/src/panopticas/constants.py b/src/panopticas/constants.py index 11c9ae8..4515e9a 100644 --- a/src/panopticas/constants.py +++ b/src/panopticas/constants.py @@ -131,6 +131,8 @@ "makefile": "Makefile", "pylintrc": "INI", "steepfile": "Ruby", + "podfile": "Ruby", # CocoaPods loads an extensionless Podfile as Ruby + "cartfile": "Cartfile", # Carthage's own syntax, a restricted subset of OGDL "cname": "CNAME", # Often GitHub et al will use a CNAME file for a URL to host from } @@ -148,6 +150,10 @@ # Swift Package Manager's lock file is JSON. Mapped by basename because # ".resolved" is shared with Carthage's Cartfile.resolved, which is not. "package.resolved": "JSON", + # Carthage's other two files share the Cartfile syntax. Mapped by basename + # because ".private" and ".resolved" mean nothing on their own. + "cartfile.private": "Cartfile", + "cartfile.resolved": "Cartfile", } # Classification of every value in EXT_FILETYPES and LANGUAGE_BY_BASENAME as @@ -233,6 +239,7 @@ "ReStructuredText", "Text", # Named single-purpose files — a filename convention, not a format + "Cartfile", "CNAME", "CODEOWNERS", "Dockerignore", @@ -313,6 +320,13 @@ "pnpm-lock.yaml": ["dependencies", "JavaScript", "pnpm", "npm"], "package.swift": ["build", "dependencies", "Swift", "SwiftPM"], "package.resolved": ["dependencies", "Swift", "SwiftPM"], + # CocoaPods and Carthage serve Swift and Objective-C alike, so + # neither carries a language tag. + "podfile": ["dependencies", "CocoaPods"], + "podfile.lock": ["dependencies", "CocoaPods"], + "cartfile": ["dependencies", "Carthage"], + "cartfile.private": ["dependencies", "Carthage"], + "cartfile.resolved": ["dependencies", "Carthage"], ".gitattributes": ["Git"], ".gitlab-ci.yml": ["pipeline", "GitLab"], # Three letter YAML extension ".gitlab-ci.yaml": ["pipeline", "GitLab"], # Full four letter YAML extension diff --git a/src/tests/Cartfile b/src/tests/Cartfile new file mode 100644 index 0000000..7e497ef --- /dev/null +++ b/src/tests/Cartfile @@ -0,0 +1 @@ +github "Alamofire/Alamofire" ~> 5.8 diff --git a/src/tests/Cartfile.private b/src/tests/Cartfile.private new file mode 100644 index 0000000..cda2697 --- /dev/null +++ b/src/tests/Cartfile.private @@ -0,0 +1 @@ +github "Quick/Quick" ~> 7.0 diff --git a/src/tests/Cartfile.resolved b/src/tests/Cartfile.resolved new file mode 100644 index 0000000..4d78432 --- /dev/null +++ b/src/tests/Cartfile.resolved @@ -0,0 +1,2 @@ +github "Alamofire/Alamofire" "5.8.1" +github "Quick/Quick" "v7.3.0" diff --git a/src/tests/Podfile b/src/tests/Podfile new file mode 100644 index 0000000..9120d73 --- /dev/null +++ b/src/tests/Podfile @@ -0,0 +1,6 @@ +platform :ios, '15.0' + +target 'Example' do + use_frameworks! + pod 'Alamofire', '~> 5.8' +end diff --git a/src/tests/Podfile.lock b/src/tests/Podfile.lock new file mode 100644 index 0000000..54a3485 --- /dev/null +++ b/src/tests/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - Alamofire (5.8.1) + +DEPENDENCIES: + - Alamofire (~> 5.8) + +SPEC REPOS: + trunk: + - Alamofire + +SPEC CHECKSUMS: + Alamofire: 0000000000000000000000000000000000000000 + +PODFILE CHECKSUM: 0000000000000000000000000000000000000000 + +COCOAPODS: 1.15.2 diff --git a/tests/test_panopticas.py b/tests/test_panopticas.py index 18479b3..b02cff6 100644 --- a/tests/test_panopticas.py +++ b/tests/test_panopticas.py @@ -83,6 +83,14 @@ def test_kotlin(self): def test_kotlin_script(self): assert get_extension_filetype(".kts") == "Kotlin" + def test_podfile_is_ruby(self): + # CocoaPods loads an extensionless Podfile as Ruby. + assert get_extension_filetype("Podfile") == "Ruby" + + def test_cartfile_has_its_own_filetype(self): + # Carthage documents its own syntax, a restricted subset of OGDL. + assert get_extension_filetype("Cartfile") == "Cartfile" + def test_objective_cpp(self): assert get_extension_filetype(".mm") == "Objective-C++" @@ -221,8 +229,15 @@ def test_swiftpm_lock_file_is_json(self): # Carthage's Cartfile.resolved shares the extension and is plain text. assert get_language_edge_cases("Package.resolved") == "JSON" - def test_other_resolved_files_are_not_claimed(self): - assert get_language_edge_cases("Cartfile.resolved") is None + def test_carthage_lock_file_is_not_json(self): + assert get_language_edge_cases("Cartfile.resolved") == "Cartfile" + + def test_carthage_private_manifest(self): + assert get_language_edge_cases("Cartfile.private") == "Cartfile" + + def test_other_resolved_and_private_files_are_not_claimed(self): + assert get_language_edge_cases("Other.resolved") is None + assert get_language_edge_cases("key.private") is None def test_go_mod_in_path(self): assert get_language_edge_cases("/some/path/go.mod") == "go.mod" @@ -415,6 +430,25 @@ def test_gradle_kotlin_dsl_in_subdirectory(self): assert "gradle" in tags assert "dependencies" in tags + @pytest.mark.parametrize("filename", ["Podfile", "Podfile.lock"]) + def test_cocoapods_files(self, filename): + assert set(get_filename_metatypes(filename)) == \ + {"dependencies", "CocoaPods"} + + @pytest.mark.parametrize("filename", [ + "Cartfile", "Cartfile.private", "Cartfile.resolved", + ]) + def test_carthage_files(self, filename): + assert set(get_filename_metatypes(filename)) == \ + {"dependencies", "Carthage"} + + def test_cocoapods_file_in_subdirectory(self): + assert "CocoaPods" in get_filename_metatypes("ios/Podfile") + + def test_podfile_lookalikes_are_not_claimed(self): + assert get_filename_metatypes("Podfile.bak") == [] + assert get_filename_metatypes("MyCartfile") == [] + def test_swiftpm_manifest(self): assert set(get_filename_metatypes("Package.swift")) == \ {"build", "dependencies", "Swift", "SwiftPM"} @@ -742,6 +776,18 @@ def test_gradle_kotlin_settings_detected(self, fixtures_exist): def test_objective_cpp_detected(self, fixtures_exist): assert get_language("file.mm") == "Objective-C++" + def test_podfile_detected(self, fixtures_exist): + assert get_language("Podfile") == "Ruby" + + def test_podfile_lock_detected(self, fixtures_exist): + assert get_language("Podfile.lock") == "Lock" + + @pytest.mark.parametrize("filename", [ + "Cartfile", "Cartfile.private", "Cartfile.resolved", + ]) + def test_carthage_files_detected(self, fixtures_exist, filename): + assert get_language(filename) == "Cartfile" + def test_swiftpm_manifest_detected(self, fixtures_exist): assert get_language("Package.swift") == "Swift" From e535ad579b91d8e358a456025845a4fe15c8e5c4 Mon Sep 17 00:00:00 2001 From: Peter Freiberg <109841928+sabbaticas@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:14:24 +1100 Subject: [PATCH 2/2] Link the CocoaPods and Carthage note to its issue Closes #42 Co-Authored-By: Claude Opus 5.5 --- changes/2026-10-cocoapods-and-carthage.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changes/2026-10-cocoapods-and-carthage.md b/changes/2026-10-cocoapods-and-carthage.md index f0f1cea..2c5bcbf 100644 --- a/changes/2026-10-cocoapods-and-carthage.md +++ b/changes/2026-10-cocoapods-and-carthage.md @@ -1,5 +1,7 @@ # CocoaPods and Carthage Detection +Closes [#42](https://github.com/kospex/panopticas/issues/42). + ## Summary Neither dependency manager's files were recognised. Only `Podfile.lock` had a