[flang][driver] Add support for `-J/-module-dir`

Add support for option -J/-module-dir in the new Flang driver.  This
will allow for including module files in other directories, as the
default search path is currently the working folder. This also provides
an option of storing the output module in the specified folder.

Differential Revision: https://reviews.llvm.org/D95448

GitOrigin-RevId: 985a42fdf8ae3117442ea129b684569fa6942a71
diff --git a/include/flang/Frontend/CompilerInstance.h b/include/flang/Frontend/CompilerInstance.h
index 79a05c0..a6f5fa9 100644
--- a/include/flang/Frontend/CompilerInstance.h
+++ b/include/flang/Frontend/CompilerInstance.h
@@ -30,6 +30,8 @@
 
   std::shared_ptr<Fortran::parser::Parsing> parsing_;
 
+  std::unique_ptr<Fortran::semantics::SemanticsContext> semanticsContext_;
+
   /// The stream for diagnostics from Semantics
   llvm::raw_ostream *semaOutputStream_ = &llvm::errs();
 
@@ -100,6 +102,9 @@
   /// }
   /// @name Semantic analysis
   /// {
+  Fortran::semantics::SemanticsContext &semanticsContext() const {
+    return *semanticsContext_;
+  }
 
   /// Replace the current stream for verbose output.
   void set_semaOutputStream(llvm::raw_ostream &Value);
diff --git a/include/flang/Frontend/CompilerInvocation.h b/include/flang/Frontend/CompilerInvocation.h
index 5136de5..bc149a3 100644
--- a/include/flang/Frontend/CompilerInvocation.h
+++ b/include/flang/Frontend/CompilerInvocation.h
@@ -11,6 +11,7 @@
 #include "flang/Frontend/FrontendOptions.h"
 #include "flang/Frontend/PreprocessorOptions.h"
 #include "flang/Parser/parsing.h"
+#include "flang/Semantics/semantics.h"
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/DiagnosticOptions.h"
 #include "llvm/Option/ArgList.h"
@@ -60,6 +61,11 @@
   // of options.
   Fortran::parser::Options parserOpts_;
 
+  /// Semantic options
+  // TODO: Merge with or translate to frontendOpts_. We shouldn't need two sets
+  // of options.
+  std::string moduleDir_ = ".";
+
 public:
   CompilerInvocation() = default;
 
@@ -69,6 +75,9 @@
   Fortran::parser::Options &fortranOpts() { return parserOpts_; }
   const Fortran::parser::Options &fortranOpts() const { return parserOpts_; }
 
+  std::string &moduleDir() { return moduleDir_; }
+  const std::string &moduleDir() const { return moduleDir_; }
+
   /// Create a compiler invocation from a list of input options.
   /// \returns true on success.
   /// \returns false if an error was encountered while parsing the arguments
@@ -87,6 +96,9 @@
   /// Set the Fortran options to user-specified values.
   /// These values are found in the preprocessor options.
   void setFortranOpts();
+
+  /// Set the Semantic Options
+  void setSemanticsOpts(Fortran::semantics::SemanticsContext &);
 };
 
 } // end namespace Fortran::frontend
diff --git a/lib/Frontend/CompilerInstance.cpp b/lib/Frontend/CompilerInstance.cpp
index 9640cd7..2a44425 100644
--- a/lib/Frontend/CompilerInstance.cpp
+++ b/lib/Frontend/CompilerInstance.cpp
@@ -24,8 +24,10 @@
     : invocation_(new CompilerInvocation()),
       allSources_(new Fortran::parser::AllSources()),
       allCookedSources_(new Fortran::parser::AllCookedSources(*allSources_)),
-      parsing_(new Fortran::parser::Parsing(*allCookedSources_)) {
-
+      parsing_(new Fortran::parser::Parsing(*allCookedSources_)),
+      semanticsContext_(new Fortran::semantics::SemanticsContext(
+          *(new Fortran::common::IntrinsicTypeDefaultKinds()),
+          *(new common::LanguageFeatureControl()), *allCookedSources_)) {
   // TODO: This is a good default during development, but ultimately we should
   // give the user the opportunity to specify this.
   allSources_->set_encoding(Fortran::parser::Encoding::UTF_8);
@@ -144,6 +146,8 @@
   invoc.SetDefaultFortranOpts();
   // Update the fortran options based on user-based input.
   invoc.setFortranOpts();
+  // Set semantic options
+  invoc.setSemanticsOpts(this->semanticsContext());
 
   // Run the frontend action `act` for every input file.
   for (const FrontendInputFile &fif : frontendOpts().inputs_) {
diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp
index 563953f..445feaa 100644
--- a/lib/Frontend/CompilerInvocation.cpp
+++ b/lib/Frontend/CompilerInvocation.cpp
@@ -221,6 +221,25 @@
     opts.searchDirectoriesFromDashI.emplace_back(currentArg->getValue());
 }
 
+/// Parses all semantic related arguments and populates the variables
+/// options accordingly.
+static void parseSemaArgs(std::string &moduleDir, llvm::opt::ArgList &args,
+    clang::DiagnosticsEngine &diags) {
+
+  auto moduleDirList =
+      args.getAllArgValues(clang::driver::options::OPT_module_dir);
+  // User can only specify -J/-module-dir once
+  // https://gcc.gnu.org/onlinedocs/gfortran/Directory-Options.html
+  if (moduleDirList.size() > 1) {
+    const unsigned diagID =
+        diags.getCustomDiagID(clang::DiagnosticsEngine::Error,
+            "Only one '-module-dir/-J' option allowed");
+    diags.Report(diagID);
+  }
+  if (moduleDirList.size() == 1)
+    moduleDir = moduleDirList[0];
+}
+
 bool CompilerInvocation::CreateFromArgs(CompilerInvocation &res,
     llvm::ArrayRef<const char *> commandLineArgs,
     clang::DiagnosticsEngine &diags) {
@@ -250,6 +269,8 @@
   ParseFrontendArgs(res.frontendOpts(), args, diags);
   // Parse the preprocessor args
   parsePreprocessorArgs(res.preprocessorOpts(), args);
+  // Parse semantic args
+  parseSemaArgs(res.moduleDir(), args, diags);
 
   return success;
 }
@@ -314,6 +335,7 @@
   auto &fortranOptions = fortranOpts();
   const auto &frontendOptions = frontendOpts();
   const auto &preprocessorOptions = preprocessorOpts();
+  auto &moduleDirJ = moduleDir();
 
   if (frontendOptions.fortranForm_ != FortranForm::Unknown) {
     fortranOptions.isFixedForm =
@@ -327,4 +349,18 @@
       fortranOptions.searchDirectories.end(),
       preprocessorOptions.searchDirectoriesFromDashI.begin(),
       preprocessorOptions.searchDirectoriesFromDashI.end());
+
+  // Add the directory supplied through -J/-module-dir to the list of search
+  // directories
+  if (moduleDirJ.compare(".") != 0)
+    fortranOptions.searchDirectories.emplace_back(moduleDirJ);
+}
+
+void CompilerInvocation::setSemanticsOpts(
+    Fortran::semantics::SemanticsContext &semaCtxt) {
+  auto &fortranOptions = fortranOpts();
+  auto &moduleDirJ = moduleDir();
+  semaCtxt.set_moduleDirectory(moduleDirJ)
+      .set_searchDirectories(fortranOptions.searchDirectories);
+  return;
 }
diff --git a/lib/Frontend/FrontendActions.cpp b/lib/Frontend/FrontendActions.cpp
index bbf8004..61e4a7d 100644
--- a/lib/Frontend/FrontendActions.cpp
+++ b/lib/Frontend/FrontendActions.cpp
@@ -109,10 +109,6 @@
 void ParseSyntaxOnlyAction::ExecuteAction() {
   CompilerInstance &ci = this->instance();
 
-  // TODO: These should be specifiable by users. For now just use the defaults.
-  common::LanguageFeatureControl features;
-  Fortran::common::IntrinsicTypeDefaultKinds defaultKinds;
-
   // Parse. In case of failure, report and return.
   ci.parsing().Parse(llvm::outs());
 
@@ -132,10 +128,8 @@
   auto &parseTree{*ci.parsing().parseTree()};
 
   // Prepare semantics
-  Fortran::semantics::SemanticsContext semanticsContext{
-      defaultKinds, features, ci.allCookedSources()};
   Fortran::semantics::Semantics semantics{
-      semanticsContext, parseTree, ci.parsing().cooked().AsCharBlock()};
+      ci.semanticsContext(), parseTree, ci.parsing().cooked().AsCharBlock()};
 
   // Run semantic checks
   semantics.Perform();
diff --git a/test/Flang-Driver/driver-help-hidden.f90 b/test/Flang-Driver/driver-help-hidden.f90
index 93fbac8..81d63b0 100644
--- a/test/Flang-Driver/driver-help-hidden.f90
+++ b/test/Flang-Driver/driver-help-hidden.f90
@@ -30,6 +30,7 @@
 ! CHECK-NEXT: -fno-color-diagnostics Disable colors in diagnostics
 ! CHECK-NEXT: -help     Display available options
 ! CHECK-NEXT: -I <dir>               Add directory to the end of the list of include search paths
+! CHECK-NEXT: -module-dir <dir>      Put MODULE files in <dir>
 ! CHECK-NEXT: -o <file> Write output to <file>
 ! CHECK-NEXT: -test-io  Run the InputOuputTest action. Use for development and testing only.
 ! CHECK-NEXT: -U <macro>             Undefine macro <macro>
diff --git a/test/Flang-Driver/driver-help.f90 b/test/Flang-Driver/driver-help.f90
index bd300df..4675dfa 100644
--- a/test/Flang-Driver/driver-help.f90
+++ b/test/Flang-Driver/driver-help.f90
@@ -30,6 +30,7 @@
 ! HELP-NEXT: -fno-color-diagnostics Disable colors in diagnostics
 ! HELP-NEXT: -help                  Display available options
 ! HELP-NEXT: -I <dir>               Add directory to the end of the list of include search paths
+! HELP-NEXT: -module-dir <dir>      Put MODULE files in <dir>
 ! HELP-NEXT: -o <file>              Write output to <file>
 ! HELP-NEXT: -U <macro>             Undefine macro <macro>
 ! HELP-NEXT: --version              Print version information
@@ -49,6 +50,7 @@
 ! HELP-FC1-NEXT: -ffree-form            Process source files in free form
 ! HELP-FC1-NEXT: -help                  Display available options
 ! HELP-FC1-NEXT: -I <dir>               Add directory to the end of the list of include search paths
+! HELP-FC1-NEXT: -module-dir <dir>      Put MODULE files in <dir>
 ! HELP-FC1-NEXT: -o <file>              Write output to <file>
 ! HELP-FC1-NEXT: -U <macro>             Undefine macro <macro>
 ! HELP-FC1-NEXT: --version              Print version information
diff --git a/test/Flang-Driver/include-module.f90 b/test/Flang-Driver/include-module.f90
index e9530cc..3abf6c8 100644
--- a/test/Flang-Driver/include-module.f90
+++ b/test/Flang-Driver/include-module.f90
@@ -7,12 +7,26 @@
 !--------------------------
 ! RUN: not %flang-new -fsyntax-only -I %S/Inputs -I %S/Inputs/module-dir %s  2>&1 | FileCheck %s --check-prefix=INCLUDED
 ! RUN: not %flang-new -fsyntax-only -I %S/Inputs %s  2>&1 | FileCheck %s --check-prefix=SINGLEINCLUDE
+! RUN: not %flang-new -fsyntax-only -I %S/Inputs -J %S/Inputs/module-dir %s 2>&1 | FileCheck %s --check-prefix=INCLUDED
+! RUN: not %flang-new -fsyntax-only -J %S/Inputs %s  2>&1 | FileCheck %s --check-prefix=SINGLEINCLUDE
+! RUN: not %flang-new -fsyntax-only -I %S/Inputs -module-dir %S/Inputs/module-dir %s  2>&1 | FileCheck %s --check-prefix=INCLUDED
+! RUN: not %flang-new -fsyntax-only -module-dir %S/Inputs %s  2>&1 | FileCheck %s --check-prefix=SINGLEINCLUDE
+! RUN: not %flang-new -fsyntax-only -J %S/Inputs/module-dir -J %S/Inputs/ %s  2>&1 | FileCheck %s --check-prefix=DOUBLEINCLUDE
+! RUN: not %flang-new -fsyntax-only -J %S/Inputs/module-dir -module-dir %S/Inputs/ %s 2>&1 | FileCheck %s --check-prefix=DOUBLEINCLUDE
+! RUN: not %flang-new -fsyntax-only -module-dir %S/Inputs/module-dir -J%S/Inputs/ %s 2>&1 | FileCheck %s --check-prefix=DOUBLEINCLUDE
 
 !-----------------------------------------
 ! FRONTEND FLANG DRIVER (flang-new -fc1)
 !-----------------------------------------
 ! RUN: not %flang-new -fc1 -fsyntax-only -I %S/Inputs -I %S/Inputs/module-dir %s  2>&1 | FileCheck %s --check-prefix=INCLUDED
 ! RUN: not %flang-new -fc1 -fsyntax-only -I %S/Inputs %s  2>&1 | FileCheck %s --check-prefix=SINGLEINCLUDE
+! RUN: not %flang-new -fc1 -fsyntax-only -I %S/Inputs -J %S/Inputs/module-dir %s 2>&1 | FileCheck %s --check-prefix=INCLUDED
+! RUN: not %flang-new -fc1 -fsyntax-only -J %S/Inputs %s  2>&1 | FileCheck %s --check-prefix=SINGLEINCLUDE
+! RUN: not %flang-new -fc1 -fsyntax-only -I %S/Inputs -module-dir %S/Inputs/module-dir %s  2>&1 | FileCheck %s --check-prefix=INCLUDED
+! RUN: not %flang-new -fc1 -fsyntax-only -module-dir %S/Inputs %s  2>&1 | FileCheck %s --check-prefix=SINGLEINCLUDE
+! RUN: not %flang-new -fc1 -fsyntax-only -J %S/Inputs/module-dir -J %S/Inputs/ %s 2>&1 | FileCheck %s --check-prefix=DOUBLEINCLUDE
+! RUN: not %flang-new -fc1 -fsyntax-only -J %S/Inputs/module-dir -module-dir %S/Inputs/ %s 2>&1 | FileCheck %s --check-prefix=DOUBLEINCLUDE
+! RUN: not %flang-new -fc1 -fsyntax-only -module-dir %S/Inputs/module-dir -J%S/Inputs/ %s 2>&1 | FileCheck %s --check-prefix=DOUBLEINCLUDE
 
 !-----------------------------------------
 ! EXPECTED OUTPUT FOR MISSING MODULE FILE
@@ -22,6 +36,11 @@
 ! SINGLEINCLUDE-NOT:error: Derived type 't1' not found
 ! SINGLEINCLUDE:error: Derived type 't2' not found
 
+!-----------------------------------------
+! EXPECTED OUTPUT FOR MISSING MODULE FILE
+!-----------------------------------------
+! DOUBLEINCLUDE:error: Only one '-module-dir/-J' option allowed
+
 !---------------------------------------
 ! EXPECTED OUTPUT FOR ALL MODULES FOUND
 !---------------------------------------
diff --git a/test/Flang-Driver/write-module.f90 b/test/Flang-Driver/write-module.f90
new file mode 100644
index 0000000..a0e7064
--- /dev/null
+++ b/test/Flang-Driver/write-module.f90
@@ -0,0 +1,10 @@
+! RUN: mkdir -p %t/dir-f18 && %f18 -fparse-only -I tools/flang/include/flang -module %t/dir-f18 %s  2>&1
+! RUN: ls %t/dir-f18/testmodule.mod && not ls %t/testmodule.mod
+
+! RUN: mkdir -p %t/dir-flang-new && %flang-new -fsyntax-only -module-dir %t/dir-flang-new %s  2>&1
+! RUN: ls %t/dir-flang-new/testmodule.mod && not ls %t/testmodule.mod
+
+module testmodule
+  type::t2
+  end type
+end