diff --git a/modcc/modcc.cpp b/modcc/modcc.cpp
index 975d35e36a680f3b7ba027af5784dd4a52467f9d..6036ac166d6e670f4e8e70da4a355cae97f20803 100644
--- a/modcc/modcc.cpp
+++ b/modcc/modcc.cpp
@@ -22,19 +22,20 @@
 
 #include <fmt/format.h>
 
-using std::cout;
-using std::cerr;
+#include <filesystem>
+
+namespace fs = std::filesystem;
 
 // Options and option parsing:
 
 int report_error(const std::string& message) {
-    cerr << red("error trace:\n") << message << "\n";
+    std::cerr << red("error trace:\n") << message << "\n";
     return 1;
 }
 
 int report_ice(const std::string& message) {
-    cerr << red("internal compiler error:\n") << message << "\n"
-         << "\nPlease report this error to the modcc developers.\n";
+    std::cerr << red("internal compiler error:\n") << message << "\n"
+              << "\nPlease report this error to the modcc developers.\n";
     return 1;
 }
 
@@ -99,22 +100,19 @@ std::ostream& operator<<(std::ostream& out, const Options& opt) {
         targets += " "+key_by_value(targetKindMap, t);
     }
 
-    for (const auto& f: opt.modfiles) {
-        out << table_prefix{"file"} << f << line_end;
-    }
-    out << table_prefix{"output"} << (opt.outprefix.empty()? "-": opt.outprefix) << line_end <<
-        table_prefix{"verbose"} << noyes[opt.verbose] << line_end <<
-        table_prefix{"targets"} << targets << line_end <<
-        table_prefix{"analysis"} << noyes[opt.analysis] << line_end;
+    for (const auto& f: opt.modfiles) out << table_prefix{"file"} << f << line_end;
+    out << table_prefix{"output"}   << (opt.outprefix.empty() ? "-" : opt.outprefix) << line_end
+        << table_prefix{"verbose"}  << noyes[opt.verbose] << line_end
+        << table_prefix{"targets"}  << targets << line_end
+        << table_prefix{"analysis"} << noyes[opt.analysis] << line_end;
     return out;
 }
 
 std::ostream& operator<<(std::ostream& out, const printer_options& popt) {
     static const std::string line_end = cyan(" |") + "\n";
-
-    return out <<
-        table_prefix{"namespace"} << popt.cpp_namespace << line_end <<
-        table_prefix{"simd"} << popt.simd << line_end;
+    out <<  table_prefix{"namespace"} << popt.cpp_namespace << line_end
+        << table_prefix{"simd"} << popt.simd << line_end;
+    return out;
 }
 
 std::istream& operator>> (std::istream& i, simd_spec& spec) {
@@ -146,8 +144,6 @@ const char* usage_str =
         "<filenames>            [Files to be compiled]\n";
 
 int main(int argc, char **argv) {
-    using namespace to;
-
     Options opt;
     printer_options popt;
     try {
@@ -188,99 +184,96 @@ int main(int argc, char **argv) {
 
     if (!opt.catalogue.empty()) popt.cpp_namespace += "::" + opt.catalogue + "_catalogue";
 
-    std::vector<std::string> modules;
+    std::vector<std::pair<std::string, std::string>> modules;
+
 
-    for (const auto& modfile: opt.modfiles) {
+    auto outdir = fs::path(opt.outprefix);
+
+    for (const auto& input: opt.modfiles) {
+        auto modfile = fs::path(input);
         try {
             auto emit_header = [&opt](const char* h) {
                 if (opt.verbose) {
-                    cout << green("[") << h << green("]") << "\n";
+                    std::cout << green("[") << h << green("]") << "\n";
                 }
             };
 
             if (opt.verbose) {
                 static const std::string tableline = cyan("."+std::string(60, '-')+".")+"\n";
-                cout << tableline;
-                cout << opt;
-                cout << popt;
-                cout << tableline;
+                std::cout << tableline
+                          << opt
+                          << popt
+                          << tableline;
             }
 
             // Load module file and initialize Module object.
             Module m(io::read_all(modfile), modfile);
 
-            if (m.empty()) {
-                return report_error("empty file: "+modfile);
-            }
+            if (m.empty()) return report_error(fmt::format("Input file is empty: {}", modfile.string()));
 
             // Perform parsing and semantic analysis passes.
-
             emit_header("parsing");
             Parser p(m, false);
-            if (!p.parse()) {
-                // Parser::parse() writes its own errors to stderr.
-                return 1;
-            }
+            if (!p.parse()) return 1;
 
             emit_header("semantic analysis");
             m.semantic();
             if (m.has_warning()) {
-                cerr << yellow("Warnings:\n");
-                cerr << m.warning_string() << "\n";
-            }
-            if (m.has_error()) {
-                return report_error(m.error_string());
+                std::cerr << yellow("Warnings:\n")
+                          << m.warning_string() << "\n";
             }
 
-            // Generate backend-specific sources for each backend provided.
+            if (m.has_error()) return report_error(m.error_string());
 
+            // Generate backend-specific sources for each backend provided.
             emit_header("code generation");
 
-            std::string prefix = m.module_name();
-            if (!opt.outprefix.empty()) {
-                if (opt.outprefix.back() != '/') opt.outprefix += "/";
-                prefix = opt.outprefix + prefix;
-            }
+            std::string mod = m.module_name();
 
             bool have_cpu = opt.targets.find(targetKind::cpu) != opt.targets.end();
             bool have_gpu = opt.targets.find(targetKind::gpu) != opt.targets.end();
 
-            io::write_all(build_info_header(m, popt, have_cpu, have_gpu), prefix+".hpp");
+            auto prefix = modfile.filename().replace_extension("").string();
+
+            io::write_all(build_info_header(m, popt, have_cpu, have_gpu), outdir / (mod + ".hpp"));
             for (targetKind target: opt.targets) {
-                std::string outfile = prefix;
                 switch (target) {
-                    case targetKind::gpu:
-                        io::write_all(emit_gpu_cpp_source(m, popt), outfile+"_gpu.cpp");
-                        io::write_all(emit_gpu_cu_source(m, popt), outfile+"_gpu.cu");
+                    case targetKind::gpu: {
+                        fs::path fn = outdir / (mod + "_gpu.cpp");
+                        io::write_all(emit_gpu_cpp_source(m, popt), fn.string());
+                        fn.replace_extension(".cu");
+                        io::write_all(emit_gpu_cu_source(m, popt), fn.string());
                         break;
-                    case targetKind::cpu:
-                        io::write_all(emit_cpp_source(m, popt), outfile+"_cpu.cpp");
+                    }
+                    case targetKind::cpu: {
+                        fs::path fn = outdir / (mod + "_cpu");
+                        fn.replace_extension(".cpp");
+                        io::write_all(emit_cpp_source(m, popt), fn.string());
                         break;
+                    }
                 }
             }
 
             // Optional analysis report.
-
             if (opt.analysis) {
-                cout << green("performance analysis\n");
+                std::cout << green("performance analysis\n");
                 for (auto &symbol: m.symbols()) {
                     if (auto method = symbol.second->is_api_method()) {
-                        cout << white("-------------------------\n");
-                        cout << yellow("method " + method->name()) << "\n";
-                        cout << white("-------------------------\n");
-
                         FlopVisitor flops;
                         method->accept(&flops);
-                        cout << white("FLOPS\n") << flops.print() << "\n";
-
                         MemOpVisitor memops;
                         method->accept(&memops);
-                        cout << white("MEMOPS\n") << memops.print() << "\n";
+
+                        std::cout << white("-------------------------\n")
+                                  << yellow("method " + method->name()) << "\n"
+                                  << white("-------------------------\n")
+                                  << white("FLOPS\n") << flops.print() << "\n"
+                                  << white("MEMOPS\n") << memops.print() << "\n";
                     }
                 }
             }
 
-            modules.push_back(m.module_name());
+            modules.push_back({mod, prefix});
         }
         catch (io::bulkio_error& e) {
             return report_error(e.what());
@@ -297,45 +290,43 @@ int main(int argc, char **argv) {
     }
 
     if (!opt.catalogue.empty()) {
-        const auto prefix = std::regex_replace(popt.cpp_namespace, std::regex{"::"}, "_");
+        const auto ns = std::regex_replace(popt.cpp_namespace, std::regex{"::"}, "_");
         {
-            std::ofstream out(opt.outprefix + opt.catalogue + "_catalogue.cpp");
+            std::ofstream out(outdir / (opt.catalogue + "_catalogue.cpp"));
             out << "// Automatically generated by modcc\n"
-                "\n"
-                "#include <arbor/mechanism_abi.h>\n"
-                "\n";
+                   "\n"
+                   "#include <arbor/mechanism_abi.h>\n"
+                   "\n";
 
-            for (const auto& mod: modules) {
-                out << fmt::format("#include \"{}.hpp\"\n", mod);
-            }
+            for (const auto& [mod, prefix]: modules) out << fmt::format("#include \"{}.hpp\"\n", mod);
 
             out << "\n"
-                "#ifdef STANDALONE\n"
-                "extern \"C\" {\n"
-                "    [[gnu::visibility(\"default\")]] const void* get_catalogue(int* n) {\n";
-            out << fmt::format("        *n = {0};\n"
+                   "#ifdef STANDALONE\n"
+                   "extern \"C\" {\n"
+                   "    [[gnu::visibility(\"default\")]] const void* get_catalogue(int* n) {\n"
+                << fmt::format("        *n = {0};\n"
                                "        static arb_mechanism cat[{0}] = {{\n",
                                opt.modfiles.size());
-            for (const auto& mod: modules) {
-                out << fmt::format("            make_{}_{}(),\n", prefix, mod);
+            for (const auto& [mod, prefix]: modules) {
+                out << fmt::format("            make_{}_{}(),\n", ns, mod);
             }
             out << "        };\n"
-                "        return (void*)cat;\n"
-                "    }\n"
-                "}\n"
-                "\n"
-                "#else\n"
-                "\n"
-                "#include <arbor/mechanism.hpp>\n"
-                "#include <arbor/assert.hpp>\n"
-                "\n";
-            out << fmt::format("#include \"{0}_catalogue.hpp\"\n"
+                   "        return (void*)cat;\n"
+                   "    }\n"
+                   "}\n"
+                   "\n"
+                   "#else\n"
+                   "\n"
+                   "#include <arbor/mechanism.hpp>\n"
+                   "#include <arbor/assert.hpp>\n"
+                   "\n"
+                << fmt::format("#include \"{0}_catalogue.hpp\"\n"
                                "\n"
                                "namespace arb {{\n"
                                "mechanism_catalogue build_{0}_catalogue() {{\n"
                                "    mechanism_catalogue cat;\n",
                                opt.catalogue);
-            for (const auto& mod: modules) {
+            for (const auto& [mod, prefix]: modules) {
                 out << fmt::format("    {{\n"
                                    "        auto mech = make_{}_{}();\n"
                                    "        auto ty = mech.type();\n"
@@ -347,22 +338,21 @@ int main(int argc, char **argv) {
                                    "        if (ic) cat.register_implementation(nm, std::make_unique<arb::mechanism>(ty, *ic));\n"
                                    "        if (ig) cat.register_implementation(nm, std::make_unique<arb::mechanism>(ty, *ig));\n"
                                    "    }}\n",
-                                   prefix, mod);
+                                   ns, mod);
             }
-
             out << "    return cat;\n"
-                "}\n"
-                "\n";
-            out << fmt::format("ARB_ARBOR_API const mechanism_catalogue& global_{0}_catalogue() {{\n"
+                   "}\n"
+                   "\n"
+                << fmt::format("ARB_ARBOR_API const mechanism_catalogue& global_{0}_catalogue() {{\n"
                                "    static mechanism_catalogue cat = build_{0}_catalogue();\n"
                                "    return cat;\n"
                                "}}\n",
-                               opt.catalogue);
-            out << "} // namespace arb\n"
-                "#endif\n";
+                               opt.catalogue)
+                << "} // namespace arb\n"
+                   "#endif\n";
         }
         {
-        std::ofstream out(opt.outprefix + opt.catalogue + "_catalogue.hpp");
+        std::ofstream out(outdir / (opt.catalogue + "_catalogue.hpp"));
         out << fmt::format("#pragma once\n"
                            "\n"
                            "#include <arbor/mechcat.hpp>\n"
diff --git a/modcc/printer/infoprinter.cpp b/modcc/printer/infoprinter.cpp
index 596b7b80d01ded90e81b6d4d1a25541ba61816dd..f59f52f544513f3d56dbcc82d9fdbd3c45f060a4 100644
--- a/modcc/printer/infoprinter.cpp
+++ b/modcc/printer/infoprinter.cpp
@@ -1,5 +1,4 @@
 #include <ostream>
-#include <set>
 #include <string>
 #include <regex>
 
@@ -15,8 +14,6 @@
 #include "io/ostream_wrappers.hpp"
 #include "io/prefixbuf.hpp"
 
-using io::quote;
-
 ARB_LIBMODCC_API std::string build_info_header(const Module& m, const printer_options& opt, bool cpu, bool gpu) {
     using io::indent;
     using io::popindent;