diff --git a/.gitignore b/.gitignore index 55c803316..2235ba614 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,8 @@ DerivedData Tests/Fixtures/.build/ # VSCode -.vscode/* \ No newline at end of file +.vscode/* + +# Bazel +bazel-* +/MODULE.bazel.lock \ No newline at end of file diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 000000000..38815bf48 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,8 @@ +module( + name = "periphery", + version = "0.0.0", + compatibility_level = 1, +) + +generated = use_extension("//bazel:extensions.bzl", "generated") +use_repo(generated, "periphery_generated") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 000000000..b9b80d4d0 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,64 @@ +{ + "lockFileVersion": 11, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/source.json": "7e3a9adf473e9af076ae485ed649d5641ad50ec5c11718103f34de03170d94ad", + "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", + "https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/source.json": "c9320aa53cd1c441d24bd6b716da087ad7e4ff0d9742a9884587596edfe53015", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/source.json": "082ed5f9837901fada8c68c2f3ddc958bb22b6d654f71dd73f3df30d45d4b749", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.11.0/source.json": "c73d9ef4268c91bd0c1cd88f1f9dfa08e814b1dbe89b5f594a9f08ba0244d206", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1", + "https://bcr.bazel.build/modules/rules_java/7.6.5/source.json": "a805b889531d1690e3c72a7a7e47a870d00323186a9904b36af83aa3d053ee8d", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/0.0.7/source.json": "355cc5737a0f294e560d52b1b7a6492d4fff2caf0bef1a315df5a298fca2d34a", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", + "https://bcr.bazel.build/modules/rules_python/0.22.1/source.json": "57226905e783bae7c37c2dd662be078728e48fa28ee4324a7eabcafb5a43d014", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.1/source.json": "a96f95e02123320aa015b956f29c00cb818fa891ef823d55148e1a362caacf29", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d" + }, + "selectedYankedVersions": {}, + "moduleExtensions": {} +} diff --git a/Sources/Frontend/Commands/BazelCommand.swift b/Sources/Frontend/Commands/BazelCommand.swift new file mode 100644 index 000000000..3f83842b5 --- /dev/null +++ b/Sources/Frontend/Commands/BazelCommand.swift @@ -0,0 +1,97 @@ +import ArgumentParser +import Foundation +import Shared +import SystemPackage + +struct BazelScanCommand: FrontendCommand { + static let configuration = CommandConfiguration( + commandName: "bazel-scan", + abstract: "Scan for unused code (for Bazel projects)" + ) + + @Option(help: "Bazel top-level targets") + var target: [String] = ["//..."] + + @Option(help: "Path to configuration file. By default Periphery will look for .periphery.yml in the current directory") + var config: FilePath = FilePath(".periphery.yml") + + @Flag(help: "Enable verbose logging") + var verbose: Bool = defaultConfiguration.$verbose.defaultValue + + private static let defaultConfiguration = Configuration() + + private static let kinds = [ + "apple_framework_packaging", + "ios_unit_test", + "ios_ui_test", + "ios_application", + // TODO: tvos, swift_binary, swift_test, etc + ] + + func run() throws { + guard let executablePath = Bundle.main.executablePath else { + // TODO: throw? + return + } + + let configuration = Configuration.shared + configuration.apply(\.$verbose, verbose) + + let buildFilePath = FilePath("/var/tmp/periphery_bazel/BUILD") + let fileManager = FileManager.default + try fileManager.createDirectory(at: buildFilePath.removingLastComponent().url, withIntermediateDirectories: true) + try? fileManager.removeItem(at: buildFilePath.url) + defer { + try? fileManager.removeItem(at: buildFilePath.url) + } + + let deps = try queryTargets().joined(separator: ",\n") + + let buildFileContents = """ + load("@periphery//bazel/internal:scan.bzl", "scan") + + scan( + name = "scan", + testonly = True, + config = "\(FilePath.makeAbsolute(config))", + periphery_binary = "\(executablePath)", + visibility = [ + "@periphery//bazel:generated" + ], + deps = [ + \(deps) + ], + ) + """ + + try buildFileContents.write(to: buildFilePath.url, atomically: true, encoding: .utf8) + + let task = Process() + // TODO: Get bazel bin path + task.launchPath = "/opt/homebrew/bin/bazel" + task.arguments = ["run", "@periphery//bazel:scan"] + try task.run() + task.waitUntilExit() + } + + // MARK: - Private + + private func queryTargets() throws -> [String] { + try Shell.shared + .exec([ + "bazel", + "query", + "--noshow_progress", + "--ui_event_filters=-info,-debug,-warning", + query + ]) + .split(separator: "\n") + .map { "\"@@\($0)\""} + } + + private var query: String { + let depsExpr = target.map { "deps(\($0))" }.joined(separator: " union ") + let kindsExpr = "kind('\(Self.kinds.joined(separator: "|")) rule', \(depsExpr))" + return kindsExpr + } +} diff --git a/Sources/Frontend/main.swift b/Sources/Frontend/main.swift index 91289fe80..6c831a7ec 100644 --- a/Sources/Frontend/main.swift +++ b/Sources/Frontend/main.swift @@ -7,7 +7,13 @@ Logger.configureBuffering() struct PeripheryCommand: FrontendCommand { static let configuration = CommandConfiguration( commandName: "periphery", - subcommands: [ScanCommand.self, CheckUpdateCommand.self, ClearCacheCommand.self, VersionCommand.self] + subcommands: [ + ScanCommand.self, + BazelScanCommand.self, + CheckUpdateCommand.self, + ClearCacheCommand.self, + VersionCommand.self + ] ) } diff --git a/Sources/Shared/Shell.swift b/Sources/Shared/Shell.swift index 778439e06..ef94ad6e7 100644 --- a/Sources/Shared/Shell.swift +++ b/Sources/Shared/Shell.swift @@ -79,7 +79,7 @@ open class Shell { result[pair.0] = pair.1 } - let preservedKeys = ["PATH", "DEVELOPER_DIR"] + let preservedKeys = ["PATH", "DEVELOPER_DIR", "SSH_AUTH_SOCK"] preservedKeys.forEach { key in if let value = environment[key] { newEnv[key] = value diff --git a/bazel/BUILD b/bazel/BUILD new file mode 100644 index 000000000..c378c2971 --- /dev/null +++ b/bazel/BUILD @@ -0,0 +1,9 @@ +package_group( + name = "generated", + includes = [ + "@periphery_generated//:package_group" + ], +) + +# TODO: Wrap with another rule to capture the log output? +alias(actual = "@periphery_generated//rule:scan", name = "scan") diff --git a/bazel/extensions.bzl b/bazel/extensions.bzl new file mode 100644 index 000000000..893e24093 --- /dev/null +++ b/bazel/extensions.bzl @@ -0,0 +1,23 @@ +def _generated_repo_impl(repository_ctx): + repository_ctx.file( + "BUILD", + content = """ +package_group( + name = "package_group", + packages = ["//..."], +) +""", + ) + + # TODO: output_base_hash like rules_xcodeproj? + # TODO: scoped by project? + repository_ctx.symlink( + "/var/tmp/periphery_bazel/BUILD", + "rule/BUILD", + ) + +generated_repo = repository_rule( + implementation = _generated_repo_impl, +) + +generated = module_extension(implementation = lambda _: generated_repo(name = "periphery_generated")) \ No newline at end of file diff --git a/bazel/internal/BUILD b/bazel/internal/BUILD new file mode 100644 index 000000000..d77caa605 --- /dev/null +++ b/bazel/internal/BUILD @@ -0,0 +1,3 @@ +exports_files([ + "scan_template.sh" +]) \ No newline at end of file diff --git a/bazel/internal/scan.bzl b/bazel/internal/scan.bzl new file mode 100644 index 000000000..6ddb9a2bc --- /dev/null +++ b/bazel/internal/scan.bzl @@ -0,0 +1,50 @@ +def _get_template_substitutions(*, periphery_binary, config_path): + """Returns the template substitutions for this executable.""" + subs = { + "periphery_binary": periphery_binary, + "config_path": config_path, + } + return {"%(" + k + ")s": subs[k] for k in subs} + +def _scan_impl(ctx): + # print(ctx.attr.config) + # print(ctx.attr.periphery_binary) + # print(ctx.file._template) + + runfiles = ctx.runfiles( + files = [], + ) + + ctx.actions.expand_template( + template = ctx.file._template, + output = ctx.outputs.scan, + substitutions = _get_template_substitutions( + periphery_binary = ctx.attr.periphery_binary, + config_path = ctx.attr.config, + ), + ) + + return DefaultInfo( + executable = ctx.outputs.scan, + files = depset( + [ctx.outputs.scan], + ), + runfiles = runfiles + ) + +scan = rule( + attrs = { + "deps": attr.label_list(mandatory = True), + "config": attr.string(), + "periphery_binary": attr.string(), + "_template": attr.label( + allow_single_file = True, + default = "@periphery//bazel/internal:scan_template.sh", + ), + }, + outputs = { + "scan": "scan.sh", + }, + implementation = _scan_impl, + executable = True, +) diff --git a/bazel/internal/scan_template.sh b/bazel/internal/scan_template.sh new file mode 100644 index 000000000..de5c7fc39 --- /dev/null +++ b/bazel/internal/scan_template.sh @@ -0,0 +1,4 @@ +echo +echo "Hello from scan_template.sh" +echo %(periphery_binary)s +echo %(config_path)s \ No newline at end of file