//===--- ArgsToFrontendOutputsConverter.cpp -------------------------------===// // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #include "swift/Frontend/ArgsToFrontendOutputsConverter.h" #include "swift/AST/DiagnosticsFrontend.h" #include "swift/Basic/Platform.h" #include "swift/Frontend/ArgsToFrontendInputsConverter.h" #include "swift/Frontend/ArgsToFrontendOptionsConverter.h" #include "swift/Frontend/Frontend.h" #include "swift/Option/Options.h" #include "swift/Option/SanitizerOptions.h" #include "swift/Strings.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Triple.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/Path.h" using namespace swift; using namespace llvm::opt; bool ArgsToFrontendOutputsConverter::convert( std::vector &mainOutputs, SupplementaryOutputPaths &supplementaryOutputs) { Optional ofc = OutputFilesComputer::create(Args, Diags, InputsAndOutputs); if (!ofc) return true; Optional> mains = ofc->computeOutputFiles(); if (!mains) return true; Optional> supplementaries = SupplementaryOutputPathsComputer(Args, Diags, InputsAndOutputs, *mains, ModuleName) .computeOutputPaths(); if (!supplementaries) return true; mainOutputs = std::move(*mains); assert(supplementaries->size() <= 1 && "Have not implemented multiple primaries yet"); if (!supplementaries->empty()) supplementaryOutputs = std::move(supplementaries->front()); return false; } Optional> ArgsToFrontendOutputsConverter::readOutputFileList(const StringRef filelistPath, DiagnosticEngine &diags) { llvm::ErrorOr> buffer = llvm::MemoryBuffer::getFile(filelistPath); if (!buffer) { diags.diagnose(SourceLoc(), diag::cannot_open_file, filelistPath, buffer.getError().message()); return None; } std::vector outputFiles; for (StringRef line : make_range(llvm::line_iterator(*buffer.get()), {})) { outputFiles.push_back(line.str()); } return outputFiles; } Optional> OutputFilesComputer::getOutputFilenamesFromCommandLineOrFilelist( const ArgList &args, DiagnosticEngine &diags) { if (const Arg *A = args.getLastArg(options::OPT_output_filelist)) { assert(!args.hasArg(options::OPT_o) && "don't use -o with -output-filelist"); return ArgsToFrontendOutputsConverter::readOutputFileList(A->getValue(), diags); } return args.getAllArgValues(options::OPT_o); } Optional OutputFilesComputer::create(const llvm::opt::ArgList &args, DiagnosticEngine &diags, const FrontendInputsAndOutputs &inputsAndOutputs) { Optional> outputArguments = getOutputFilenamesFromCommandLineOrFilelist(args, diags); if (!outputArguments) return None; const StringRef outputDirectoryArgument = outputArguments->size() == 1 && llvm::sys::fs::is_directory(outputArguments->front()) ? StringRef(outputArguments->front()) : StringRef(); ArrayRef outputFileArguments = outputDirectoryArgument.empty() ? ArrayRef(*outputArguments) : ArrayRef(); const StringRef firstInput = inputsAndOutputs.hasSingleInput() ? inputsAndOutputs.getFilenameOfFirstInput() : StringRef(); const FrontendOptions::ActionType requestedAction = ArgsToFrontendOptionsConverter::determineRequestedAction(args); if (!outputFileArguments.empty() && outputFileArguments.size() != inputsAndOutputs.countOfInputsProducingMainOutputs()) { diags.diagnose( SourceLoc(), diag::error_if_any_output_files_are_specified_they_all_must_be); return None; } return OutputFilesComputer( args, diags, inputsAndOutputs, std::move(outputFileArguments), outputDirectoryArgument, firstInput, requestedAction, args.getLastArg(options::OPT_module_name), FrontendOptions::suffixForPrincipalOutputFileForAction(requestedAction), FrontendOptions::doesActionProduceTextualOutput(requestedAction)); } OutputFilesComputer::OutputFilesComputer( const llvm::opt::ArgList &args, DiagnosticEngine &diags, const FrontendInputsAndOutputs &inputsAndOutputs, std::vector outputFileArguments, const StringRef outputDirectoryArgument, const StringRef firstInput, const FrontendOptions::ActionType requestedAction, const llvm::opt::Arg *moduleNameArg, const StringRef suffix, const bool hasTextualOutput) : Args(args), Diags(diags), InputsAndOutputs(inputsAndOutputs), OutputFileArguments(outputFileArguments), OutputDirectoryArgument(outputDirectoryArgument), FirstInput(firstInput), RequestedAction(requestedAction), ModuleNameArg(moduleNameArg), Suffix(suffix), HasTextualOutput(hasTextualOutput) {} Optional> OutputFilesComputer::computeOutputFiles() const { std::vector outputFiles; bool hadError = false; unsigned i = 0; InputsAndOutputs.forEachInputProducingAMainOutputFile( [&](const InputFile &input) -> void { StringRef outputArg = OutputFileArguments.empty() ? StringRef() : StringRef(OutputFileArguments[i++]); Optional outputFile = computeOutputFile(outputArg, input); if (!outputFile) { hadError = true; return; } outputFiles.push_back(*outputFile); }); return hadError ? None : Optional>(outputFiles); } Optional OutputFilesComputer::computeOutputFile(StringRef outputArg, const InputFile &input) const { // Return an empty string to signify no output. // The frontend does not currently produce a diagnostic // if a -o argument is present for such an action // for instance swiftc -frontend -o foo -interpret foo.swift if (!FrontendOptions::doesActionProduceOutput(RequestedAction)) return std::string(); if (!OutputDirectoryArgument.empty()) return deriveOutputFileForDirectory(input); if (!outputArg.empty()) return outputArg.str(); return deriveOutputFileFromInput(input); } Optional OutputFilesComputer::deriveOutputFileFromInput(const InputFile &input) const { if (input.file() == "-" || HasTextualOutput) return std::string("-"); std::string baseName = determineBaseNameOfOutput(input); if (baseName.empty()) { // Assuming FrontendOptions::doesActionProduceOutput(RequestedAction) Diags.diagnose(SourceLoc(), diag::error_no_output_filename_specified); return None; } return deriveOutputFileFromParts("", baseName); } Optional OutputFilesComputer::deriveOutputFileForDirectory( const InputFile &input) const { std::string baseName = determineBaseNameOfOutput(input); if (baseName.empty()) { Diags.diagnose(SourceLoc(), diag::error_implicit_output_file_is_directory, OutputDirectoryArgument); return None; } return deriveOutputFileFromParts(OutputDirectoryArgument, baseName); } std::string OutputFilesComputer::determineBaseNameOfOutput(const InputFile &input) const { StringRef nameToStem = input.isPrimary() ? input.file() : ModuleNameArg ? ModuleNameArg->getValue() : FirstInput; return llvm::sys::path::stem(nameToStem).str(); } std::string OutputFilesComputer::deriveOutputFileFromParts(StringRef dir, StringRef base) const { assert(!base.empty()); llvm::SmallString<128> path(dir); llvm::sys::path::append(path, base); llvm::sys::path::replace_extension(path, Suffix); return path.str(); } SupplementaryOutputPathsComputer::SupplementaryOutputPathsComputer( const ArgList &args, DiagnosticEngine &diags, const FrontendInputsAndOutputs &inputsAndOutputs, ArrayRef outputFiles, StringRef moduleName) : Args(args), Diags(diags), InputsAndOutputs(inputsAndOutputs), OutputFiles(outputFiles), ModuleName(moduleName), RequestedAction( ArgsToFrontendOptionsConverter::determineRequestedAction(Args)) {} Optional> SupplementaryOutputPathsComputer::computeOutputPaths() const { Optional> pathsFromUser = getSupplementaryOutputPathsFromArguments(); if (!pathsFromUser) return None; if (InputsAndOutputs.hasPrimaryInputs()) assert(OutputFiles.size() == pathsFromUser->size()); else if (InputsAndOutputs.isSingleThreadedWMO()) assert(OutputFiles.size() == pathsFromUser->size() && pathsFromUser->size() == 1); else { // Multi-threaded WMO is the exception assert(OutputFiles.size() == InputsAndOutputs.inputCount() && pathsFromUser->size() == InputsAndOutputs.hasInputs() ? 1 : 0); } std::vector outputPaths; unsigned i = 0; bool hadError = false; InputsAndOutputs.forEachInputProducingSupplementaryOutput( [&](const InputFile &input) -> void { if (auto suppPaths = computeOutputPathsForOneInput( OutputFiles[i], (*pathsFromUser)[i], input)) outputPaths.push_back(*suppPaths); else hadError = true; ++i; }); if (hadError) return None; return outputPaths; } Optional> SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments() const { auto objCHeaderOutput = getSupplementaryFilenamesFromArguments( options::OPT_emit_objc_header_path); auto moduleOutput = getSupplementaryFilenamesFromArguments(options::OPT_emit_module_path); auto moduleDocOutput = getSupplementaryFilenamesFromArguments(options::OPT_emit_module_doc_path); auto dependenciesFile = getSupplementaryFilenamesFromArguments( options::OPT_emit_dependencies_path); auto referenceDependenciesFile = getSupplementaryFilenamesFromArguments( options::OPT_emit_reference_dependencies_path); auto serializedDiagnostics = getSupplementaryFilenamesFromArguments( options::OPT_serialize_diagnostics_path); auto loadedModuleTrace = getSupplementaryFilenamesFromArguments( options::OPT_emit_loaded_module_trace_path); auto TBD = getSupplementaryFilenamesFromArguments(options::OPT_emit_tbd_path); if (!objCHeaderOutput || !moduleOutput || !moduleDocOutput || !dependenciesFile || !referenceDependenciesFile || !serializedDiagnostics || !loadedModuleTrace || !TBD) { return None; } std::vector result; const unsigned N = InputsAndOutputs.countOfFilesProducingSupplementaryOutput(); for (unsigned i = 0; i < N; ++i) { SupplementaryOutputPaths sop; sop.ObjCHeaderOutputPath = (*objCHeaderOutput)[i]; sop.ModuleOutputPath = (*moduleOutput)[i]; sop.ModuleDocOutputPath = (*moduleDocOutput)[i]; sop.DependenciesFilePath = (*dependenciesFile)[i]; sop.ReferenceDependenciesFilePath = (*referenceDependenciesFile)[i]; sop.SerializedDiagnosticsPath = (*serializedDiagnostics)[i]; sop.LoadedModuleTracePath = (*loadedModuleTrace)[i]; sop.TBDPath = (*TBD)[i]; result.push_back(sop); } return result; } // Extend this routine for filelists if/when we have them. Optional> SupplementaryOutputPathsComputer::getSupplementaryFilenamesFromArguments( options::ID pathID) const { std::vector paths = Args.getAllArgValues(pathID); const unsigned N = InputsAndOutputs.countOfFilesProducingSupplementaryOutput(); if (paths.size() == N) return paths; if (paths.size() == 0) return std::vector(N, std::string()); Diags.diagnose(SourceLoc(), diag::error_wrong_number_of_arguments, Args.getLastArg(pathID)->getOption().getPrefixedName(), N, paths.size()); return None; } Optional SupplementaryOutputPathsComputer::computeOutputPathsForOneInput( StringRef outputFile, const SupplementaryOutputPaths &pathsFromArguments, const InputFile &input) const { StringRef defaultSupplementaryOutputPathExcludingExtension = deriveDefaultSupplementaryOutputPathExcludingExtension(outputFile, input); using namespace options; auto dependenciesFilePath = determineSupplementaryOutputFilename( OPT_emit_dependencies, pathsFromArguments.DependenciesFilePath, "d", "", defaultSupplementaryOutputPathExcludingExtension); auto referenceDependenciesFilePath = determineSupplementaryOutputFilename( OPT_emit_reference_dependencies, pathsFromArguments.ReferenceDependenciesFilePath, "swiftdeps", "", defaultSupplementaryOutputPathExcludingExtension); auto serializedDiagnosticsPath = determineSupplementaryOutputFilename( OPT_serialize_diagnostics, pathsFromArguments.SerializedDiagnosticsPath, "dia", "", defaultSupplementaryOutputPathExcludingExtension); auto objcHeaderOutputPath = determineSupplementaryOutputFilename( OPT_emit_objc_header, pathsFromArguments.ObjCHeaderOutputPath, "h", "", defaultSupplementaryOutputPathExcludingExtension); auto loadedModuleTracePath = determineSupplementaryOutputFilename( OPT_emit_loaded_module_trace, pathsFromArguments.LoadedModuleTracePath, "trace.json", "", defaultSupplementaryOutputPathExcludingExtension); auto tbdPath = determineSupplementaryOutputFilename( OPT_emit_tbd, pathsFromArguments.TBDPath, "tbd", "", defaultSupplementaryOutputPathExcludingExtension); auto moduleDocOutputPath = determineSupplementaryOutputFilename( OPT_emit_module_doc, pathsFromArguments.ModuleDocOutputPath, SERIALIZED_MODULE_DOC_EXTENSION, "", defaultSupplementaryOutputPathExcludingExtension); ID emitModuleOption; std::string moduleExtension; std::string mainOutputIfUsableForModule; deriveModulePathParameters(emitModuleOption, moduleExtension, mainOutputIfUsableForModule); auto moduleOutputPath = determineSupplementaryOutputFilename( emitModuleOption, pathsFromArguments.ModuleOutputPath, moduleExtension, mainOutputIfUsableForModule, defaultSupplementaryOutputPathExcludingExtension); SupplementaryOutputPaths sop; sop.ObjCHeaderOutputPath = objcHeaderOutputPath; sop.ModuleOutputPath = moduleOutputPath; sop.ModuleDocOutputPath = moduleDocOutputPath; sop.DependenciesFilePath = dependenciesFilePath; sop.ReferenceDependenciesFilePath = referenceDependenciesFilePath; sop.SerializedDiagnosticsPath = serializedDiagnosticsPath; sop.LoadedModuleTracePath = loadedModuleTracePath; sop.TBDPath = tbdPath; return sop; } StringRef SupplementaryOutputPathsComputer:: deriveDefaultSupplementaryOutputPathExcludingExtension( StringRef outputFilename, const InputFile &input) const { // Put the supplementary output file next to the output file if possible. if (!outputFilename.empty() && outputFilename != "-") return outputFilename; if (input.isPrimary() && input.file() != "-") return llvm::sys::path::filename(input.file()); return ModuleName; } std::string SupplementaryOutputPathsComputer::determineSupplementaryOutputFilename( options::ID emitOpt, std::string pathFromArguments, StringRef extension, StringRef mainOutputIfUsable, StringRef defaultSupplementaryOutputPathExcludingExtension) const { if (!pathFromArguments.empty()) return pathFromArguments; if (!Args.hasArg(emitOpt)) return std::string(); if (!mainOutputIfUsable.empty()) { return mainOutputIfUsable.str(); } llvm::SmallString<128> path(defaultSupplementaryOutputPathExcludingExtension); llvm::sys::path::replace_extension(path, extension); return path.str().str(); }; void SupplementaryOutputPathsComputer::deriveModulePathParameters( options::ID &emitOption, std::string &extension, std::string &mainOutputIfUsable) const { bool isSIB = RequestedAction == FrontendOptions::ActionType::EmitSIB || RequestedAction == FrontendOptions::ActionType::EmitSIBGen; emitOption = !isSIB ? options::OPT_emit_module : RequestedAction == FrontendOptions::ActionType::EmitSIB ? options::OPT_emit_sib : options::OPT_emit_sibgen; bool canUseMainOutputForModule = RequestedAction == FrontendOptions::ActionType::MergeModules || RequestedAction == FrontendOptions::ActionType::EmitModuleOnly || isSIB; extension = isSIB ? SIB_EXTENSION : SERIALIZED_MODULE_EXTENSION; mainOutputIfUsable = canUseMainOutputForModule && !OutputFiles.empty() ? OutputFiles[0] : ""; }