Skip to content

Commit ffb87d9

Browse files
committed
Add option to adjust platforms from command line
The set of platforms in an analysis can now be specified using: --platform <platform> When combined with a configuration file, this new option can be used to limit analysis to a subset of platforms, as below: -c config.yaml --platform CPU When the platform specification is a .json file, it is interpreted as a definition of a new platform: --platform /path/to/cpu.json --platform /path/to/gpu.json Both options are intended to improve user productivity, by minimizing the amount of time spent editing configuration files when making minor adjustments to an existing analysis. Signed-off-by: John Pennycook <[email protected]>
1 parent 8b8105b commit ffb87d9

File tree

3 files changed

+113
-21
lines changed

3 files changed

+113
-21
lines changed

codebasin.py

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ def main():
110110
help="Exclude files matching this pattern from the code base. "
111111
+ "May be specified multiple times.",
112112
)
113+
parser.add_argument(
114+
"-p",
115+
"--platform",
116+
dest="platforms",
117+
metavar="<platform>",
118+
action="append",
119+
default=[],
120+
help="Add the specified platform to the analysis. "
121+
+ "May be a name or a path to a compilation database. "
122+
+ "May be specified multiple times. "
123+
+ "If not specified, all known platforms will be included.",
124+
)
125+
113126
args = parser.parse_args()
114127

115128
stdout_log = logging.StreamHandler(sys.stdout)
@@ -120,25 +133,76 @@ def main():
120133
)
121134
rootdir = os.path.realpath(args.rootdir)
122135

123-
if args.config_file is None:
136+
# Process the -p flag first to infer wider context.
137+
filtered_platforms = []
138+
additional_platforms = []
139+
for p in args.platforms:
140+
# If it's a path, it has to be a compilation database.
141+
if os.path.exists(p):
142+
if not os.path.splitext(p)[1] == ".json":
143+
raise RuntimeError(f"Platform file {p} must end in .json.")
144+
additional_platforms.append(p)
145+
continue
146+
147+
# Otherwise, treat it as a name in the configuration file.
148+
if not isinstance(p, str):
149+
raise RuntimeError(f"Platform name {p} must be a string.")
150+
151+
# Explain the logic above in cases that look suspiciously like paths.
152+
if "/" in p or os.path.splitext(p)[1] == ".json":
153+
logging.getLogger("codebasin").warning(
154+
f"{p} doesn't exist, so will be treated as a name.",
155+
)
156+
filtered_platforms.append(p)
157+
158+
# If no additional platforms are specified, a config file is required.
159+
config_file = args.config_file
160+
if len(additional_platforms) == 0 and config_file is None:
124161
config_file = os.path.join(rootdir, "config.yaml")
125-
else:
126-
config_file = args.config_file
127-
# Load the configuration file into a dict
128-
if not util.ensure_yaml(config_file):
129-
logging.getLogger("codebasin").error(
130-
"Configuration file does not have YAML file extension.",
162+
if not os.path.exists(config_file):
163+
raise RuntimeError(f"Could not find config file {config_file}")
164+
165+
# Set up a default codebase and configuration object.
166+
codebase = {
167+
"files": [],
168+
"platforms": [],
169+
"exclude_files": set(),
170+
"exclude_patterns": args.excludes,
171+
"rootdir": rootdir,
172+
}
173+
configuration = {}
174+
175+
# Load the configuration file if it exists, obeying any platform filter.
176+
if config_file is not None:
177+
if not util.ensure_yaml(config_file):
178+
logging.getLogger("codebasin").error(
179+
"Configuration file does not have YAML file extension.",
180+
)
181+
sys.exit(1)
182+
codebase, configuration = config.load(
183+
config_file,
184+
rootdir,
185+
exclude_patterns=args.excludes,
186+
filtered_platforms=filtered_platforms,
131187
)
132-
sys.exit(1)
133-
codebase, configuration = config.load(
134-
config_file,
135-
rootdir,
136-
exclude_patterns=args.excludes,
137-
)
188+
189+
# Extend configuration with any additional platforms.
190+
for p in additional_platforms:
191+
name = os.path.splitext(os.path.basename(p))[0]
192+
if name in codebase["platforms"]:
193+
raise RuntimeError(f"Platform name {p} conflicts with {name}.")
194+
db = config.load_database(p, rootdir)
195+
configuration.update({name: db})
138196

139197
# Parse the source tree, and determine source line associations.
140198
# The trees and associations are housed in state.
141-
state = finder.find(rootdir, codebase, configuration)
199+
legacy_warnings = True if config_file else False
200+
state = finder.find(
201+
rootdir,
202+
codebase,
203+
configuration,
204+
legacy_warnings=legacy_warnings,
205+
)
142206

143207
# Count lines for platforms
144208
platform_mapper = PlatformMapper(codebase)
@@ -172,7 +236,11 @@ def report_enabled(name):
172236

173237
# Print clustering report
174238
if report_enabled("clustering"):
175-
output_prefix = os.path.realpath(guess_project_name(config_file))
239+
if config_file is None:
240+
platform_names = [p[0] for p in args.platforms]
241+
output_prefix = "-".join(platform_names)
242+
else:
243+
output_prefix = os.path.realpath(guess_project_name(config_file))
176244
clustering_output_name = output_prefix + "-dendrogram.png"
177245
clustering = report.clustering(clustering_output_name, setmap)
178246
if clustering is not None:

codebasin/config.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,13 @@ def load_platform(config, rootdir, platform_name):
571571
return configuration
572572

573573

574-
def load(config_file, rootdir, *, exclude_patterns=None):
574+
def load(
575+
config_file,
576+
rootdir,
577+
*,
578+
exclude_patterns=None,
579+
filtered_platforms=None,
580+
):
575581
"""
576582
Load the configuration file into Python objects.
577583
Return a (codebase, platform configuration) tuple of dicts.
@@ -597,6 +603,16 @@ def load(config_file, rootdir, *, exclude_patterns=None):
597603

598604
log.info("Platforms: %s", ", ".join(codebase["platforms"]))
599605

606+
# Limit the set of platforms in the codebase if requested.
607+
if filtered_platforms:
608+
for p in filtered_platforms:
609+
if p not in codebase["platforms"]:
610+
raise RuntimeError(
611+
f"Platform {p} requested on the command line "
612+
+ "does not exist in the configuration file.",
613+
)
614+
codebase["platforms"] = filtered_platforms
615+
600616
# Read each platform definition and populate platform configuration
601617
# If files was empty, populate it with the files we find here
602618
populate_files = not codebase["files"]

codebasin/finder.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,14 @@ def get_map(self, fn):
127127
return self.maps[fn]
128128

129129

130-
def find(rootdir, codebase, configuration, *, summarize_only=True):
130+
def find(
131+
rootdir,
132+
codebase,
133+
configuration,
134+
*,
135+
summarize_only=True,
136+
legacy_warnings=True,
137+
):
131138
"""
132139
Find codepaths in the files provided and return a mapping of source
133140
lines to platforms.
@@ -141,10 +148,11 @@ def find(rootdir, codebase, configuration, *, summarize_only=True):
141148
for e in configuration[p]:
142149
if e["file"] not in codebase["files"]:
143150
filename = e["file"]
144-
log.warning(
145-
f"{filename} found in definition of platform {p} "
146-
+ "but missing from codebase",
147-
)
151+
if legacy_warnings:
152+
log.warning(
153+
f"{filename} found in definition of platform {p} "
154+
+ "but missing from codebase",
155+
)
148156
state.insert_file(e["file"])
149157

150158
# Process each tree, by associating nodes with platforms

0 commit comments

Comments
 (0)