diff --git a/include/swift/AST/PrintOptions.h b/include/swift/AST/PrintOptions.h index e8121e866d4..f597668778c 100644 --- a/include/swift/AST/PrintOptions.h +++ b/include/swift/AST/PrintOptions.h @@ -424,6 +424,8 @@ struct PrintOptions { return result; } + static PrintOptions printTextualInterfaceFile(); + static PrintOptions printModuleInterface(); static PrintOptions printTypeInterface(Type T); diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 1eaa286395b..a4013820b0f 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -64,6 +64,21 @@ void PrintOptions::clearSynthesizedExtension() { TransformContext.reset(); } +PrintOptions PrintOptions::printTextualInterfaceFile() { + PrintOptions result; + result.PrintLongAttrsOnSeparateLines = true; + result.TypeDefinitions = true; + result.PrintIfConfig = false; + result.FullyQualifiedTypes = true; + result.SkipImports = true; + result.AccessFilter = AccessLevel::Public; + + // FIXME: We'll need the actual default parameter expression. + result.PrintDefaultParameterPlaceholder = false; + + return result; +} + TypeTransformContext::TypeTransformContext(Type T) : BaseType(T.getPointer()) { assert(T->mayHaveMembers()); diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index 606f18f3e6f..57703bce3a4 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -313,49 +313,89 @@ static bool writeSIL(SILModule &SM, const PrimarySpecificPaths &PSPs, PSPs.OutputFilename, opts.EmitSortedSIL); } -static bool printAsObjCIfNeeded(StringRef outputPath, ModuleDecl *M, - StringRef bridgingHeader, bool moduleIsPublic) { - using namespace llvm::sys; - +/// Invokes \p action with a raw_ostream that refers to a temporary file, which +/// is then renamed into place as \p outputPath when the action completes. +/// +/// If a temporary file cannot be created for whatever reason, \p action will +/// be invoked with a stream directly opened at \p outputPath. Otherwise, if +/// there is already a file at \p outputPath, it will not be overwritten if +/// the new contents are identical. +/// +/// If the process is interrupted with a signal, any temporary file will be +/// removed. +/// +/// As a special case, an output path of "-" is treated as referring to stdout. +/// +/// If an error occurs, it is reported via \p diags. +static bool atomicallyWritingToTextFile( + StringRef outputPath, DiagnosticEngine &diags, + llvm::function_ref action) { if (outputPath.empty()) return false; - clang::CompilerInstance Clang; - std::string tmpFilePath; - std::error_code EC; - std::unique_ptr out = - Clang.createOutputFile(outputPath, EC, - /*Binary=*/false, - /*RemoveFileOnSignal=*/true, - /*BaseInput=*/"", - path::extension(outputPath), - /*UseTemporary=*/true, - /*CreateMissingDirectories=*/false, - /*ResultPathName=*/nullptr, - &tmpFilePath); + bool hadError; + { + clang::CompilerInstance Clang; - if (!out) { - M->getASTContext().Diags.diagnose(SourceLoc(), diag::error_opening_output, - tmpFilePath, EC.message()); - return true; + std::error_code EC; + std::unique_ptr out = + Clang.createOutputFile(outputPath, EC, + /*Binary=*/false, + /*RemoveFileOnSignal=*/true, + /*BaseInput=*/"", + llvm::sys::path::extension(outputPath), + /*UseTemporary=*/true, + /*CreateMissingDirectories=*/false, + /*ResultPathName=*/nullptr, + &tmpFilePath); + + if (!out) { + diags.diagnose(SourceLoc(), diag::error_opening_output, + tmpFilePath, EC.message()); + return true; + } + + hadError = action(*out); } - auto requiredAccess = moduleIsPublic ? AccessLevel::Public - : AccessLevel::Internal; - bool hadError = printAsObjC(*out, M, bridgingHeader, requiredAccess); - out->flush(); - - EC = swift::moveFileIfDifferent(tmpFilePath, outputPath); - if (EC) { - M->getASTContext().Diags.diagnose(SourceLoc(), diag::error_opening_output, - outputPath, EC.message()); - return true; + if (!tmpFilePath.empty()) { + if (auto EC = swift::moveFileIfDifferent(tmpFilePath, outputPath)) { + diags.diagnose(SourceLoc(), diag::error_opening_output, + outputPath, EC.message()); + return true; + } } return hadError; } +static bool printAsObjCIfNeeded(StringRef outputPath, ModuleDecl *M, + StringRef bridgingHeader, bool moduleIsPublic) { + return atomicallyWritingToTextFile(outputPath, M->getDiags(), + [&](raw_ostream &out) -> bool { + auto requiredAccess = moduleIsPublic ? AccessLevel::Public + : AccessLevel::Internal; + return printAsObjC(out, M, bridgingHeader, requiredAccess); + }); +} + +static bool printModuleInterfaceIfNeeded(StringRef outputPath, ModuleDecl *M) { + return atomicallyWritingToTextFile(outputPath, M->getDiags(), + [&](raw_ostream &out) -> bool { + auto printOptions = PrintOptions::printTextualInterfaceFile(); + SmallVector topLevelDecls; + M->getTopLevelDecls(topLevelDecls); + for (const Decl *D : topLevelDecls) { + if (!D->shouldPrintInContext(printOptions)) + continue; + D->print(out, printOptions); + out << "\n"; + } + return false; + }); +} + /// Returns the OutputKind for the given Action. static IRGenOutputKind getOutputKind(FrontendOptions::ActionType Action) { switch (Action) { @@ -1321,6 +1361,10 @@ static bool performCompileStepsPostSILGen( Instance.getMainModule(), opts.ImplicitObjCHeaderPath, moduleIsPublic); + (void)printModuleInterfaceIfNeeded( + PSPs.SupplementaryOutputs.ModuleInterfaceOutputPath, + Instance.getMainModule()); + if (Action == FrontendOptions::ActionType::EmitSIB) return serializeSIB(SM.get(), PSPs, Instance.getASTContext(), MSF); diff --git a/test/ModuleInterface/Inputs/other.swift b/test/ModuleInterface/Inputs/other.swift new file mode 100644 index 00000000000..af473d54d48 --- /dev/null +++ b/test/ModuleInterface/Inputs/other.swift @@ -0,0 +1 @@ +public func otherFileFunction() {} \ No newline at end of file diff --git a/test/ModuleInterface/print-from-partial-modules.swift b/test/ModuleInterface/print-from-partial-modules.swift new file mode 100644 index 00000000000..7e5c51d174e --- /dev/null +++ b/test/ModuleInterface/print-from-partial-modules.swift @@ -0,0 +1,9 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -o %t/main~partial.swiftmodule -primary-file %s %S/Inputs/other.swift -module-name main +// RUN: %target-swift-frontend -emit-module -o %t/other~partial.swiftmodule %s -primary-file %S/Inputs/other.swift -module-name main +// RUN: %target-swift-frontend -merge-modules -emit-module -o /dev/null -emit-interface-path - %t/main~partial.swiftmodule -module-name main %t/other~partial.swiftmodule | %FileCheck %s + +// CHECK: {{^}}func verySimpleFunction(){{$}} +public func verySimpleFunction() {} + +// CHECK: {{^}}func otherFileFunction(){{$}} diff --git a/test/ModuleInterface/smoke-test.swift b/test/ModuleInterface/smoke-test.swift new file mode 100644 index 00000000000..7c65849d608 --- /dev/null +++ b/test/ModuleInterface/smoke-test.swift @@ -0,0 +1,8 @@ +// RUN: %target-swift-frontend -emit-interface-path - -emit-module -o /dev/null %s | %FileCheck %s +// RUN: %target-swift-frontend -emit-interface-path - -emit-module -o /dev/null %s %S/Inputs/other.swift | %FileCheck -check-prefix CHECK-MULTI-FILE %s + +// CHECK: public func verySimpleFunction(){{$}} +// CHECK-MULTI-FILE: public func verySimpleFunction(){{$}} +public func verySimpleFunction() {} + +// CHECK-MULTI-FILE: public func otherFileFunction(){{$}}