Merge pull request #34231 from CodaFi/skippy

Skip Merge-Modules When We Can
This commit is contained in:
Robert Widmann
2020-10-08 14:41:01 -07:00
committed by GitHub
7 changed files with 194 additions and 57 deletions

View File

@@ -130,15 +130,28 @@ public:
class IncrementalJobAction : public JobAction { class IncrementalJobAction : public JobAction {
public: public:
struct InputInfo { struct InputInfo {
enum Status { /// The status of an input known to the driver. These are used to affect
/// the scheduling decisions made during an incremental build.
///
/// \Note The order of cases matters. They are ordered from least to
/// greatest impact on the incremental build schedule.
enum class Status {
/// The input to this job is up to date.
UpToDate, UpToDate,
NeedsCascadingBuild, /// The input to this job has changed in a way that requires this job to
/// be rerun, but not in such a way that it requires a cascading rebuild.
NeedsNonCascadingBuild, NeedsNonCascadingBuild,
/// The input to this job has changed in a way that requires this job to
/// be rerun, and in such a way that all jobs dependent upon this one
/// must be scheduled as well.
NeedsCascadingBuild,
/// The input to this job was not known to the driver when it was last
/// run.
NewlyAdded NewlyAdded
}; };
public: public:
Status status = UpToDate; Status status = Status::UpToDate;
llvm::sys::TimePoint<> previousModTime; llvm::sys::TimePoint<> previousModTime;
InputInfo() = default; InputInfo() = default;
@@ -146,7 +159,11 @@ public:
: status(stat), previousModTime(time) {} : status(stat), previousModTime(time) {}
static InputInfo makeNewlyAdded() { static InputInfo makeNewlyAdded() {
return InputInfo(Status::NewlyAdded, llvm::sys::TimePoint<>::max()); return {Status::NewlyAdded, llvm::sys::TimePoint<>::max()};
}
static InputInfo makeNeedsCascadingRebuild() {
return {Status::NeedsCascadingBuild, llvm::sys::TimePoint<>::min()};
} }
}; };
@@ -166,7 +183,8 @@ public:
public: public:
static bool classof(const Action *A) { static bool classof(const Action *A) {
return A->getKind() == Action::Kind::CompileJob; return A->getKind() == Action::Kind::CompileJob ||
A->getKind() == Action::Kind::MergeModuleJob;
} }
}; };
@@ -263,12 +281,12 @@ public:
} }
}; };
class MergeModuleJobAction : public JobAction { class MergeModuleJobAction : public IncrementalJobAction {
virtual void anchor() override; virtual void anchor() override;
public: public:
MergeModuleJobAction(ArrayRef<const Action *> Inputs) MergeModuleJobAction(ArrayRef<const Action *> Inputs, InputInfo input)
: JobAction(Action::Kind::MergeModuleJob, Inputs, : IncrementalJobAction(Action::Kind::MergeModuleJob, Inputs,
file_types::TY_SwiftModuleFile) {} file_types::TY_SwiftModuleFile, input) {}
static bool classof(const Action *A) { static bool classof(const Action *A) {
return A->getKind() == Action::Kind::MergeModuleJob; return A->getKind() == Action::Kind::MergeModuleJob;

View File

@@ -446,7 +446,7 @@ namespace driver {
std::vector<const Job*> std::vector<const Job*>
reloadAndRemarkDeps(const Job *FinishedCmd, int ReturnCode, reloadAndRemarkDeps(const Job *FinishedCmd, int ReturnCode,
const bool forRanges) { const bool forRanges) {
const CommandOutput &Output = FinishedCmd->getOutput(); const CommandOutput &Output = FinishedCmd->getOutput();
StringRef DependenciesFile = StringRef DependenciesFile =
Output.getAdditionalOutputForType(file_types::TY_SwiftDeps); Output.getAdditionalOutputForType(file_types::TY_SwiftDeps);
@@ -458,7 +458,8 @@ namespace driver {
// coarse dependencies that always affect downstream nodes), but we're // coarse dependencies that always affect downstream nodes), but we're
// not using either of those right now, and this logic should probably // not using either of those right now, and this logic should probably
// be revisited when we are. // be revisited when we are.
assert(FinishedCmd->getCondition() == Job::Condition::Always); assert(isa<MergeModuleJobAction>(FinishedCmd->getSource()) ||
FinishedCmd->getCondition() == Job::Condition::Always);
return {}; return {};
} }
const bool compileExitedNormally = const bool compileExitedNormally =
@@ -907,6 +908,7 @@ namespace driver {
return everyIncrementalJob; return everyIncrementalJob;
}; };
const Job *mergeModulesJob = nullptr;
CommandSet jobsToSchedule; CommandSet jobsToSchedule;
CommandSet initialCascadingCommands; CommandSet initialCascadingCommands;
for (const Job *cmd : Comp.getJobs()) { for (const Job *cmd : Comp.getJobs()) {
@@ -915,6 +917,11 @@ namespace driver {
continue; continue;
} }
if (isa<MergeModuleJobAction>(cmd->getSource())) {
assert(!mergeModulesJob && "multiple scheduled merge-modules jobs?");
mergeModulesJob = cmd;
}
const Optional<std::pair<bool, bool>> shouldSchedAndIsCascading = const Optional<std::pair<bool, bool>> shouldSchedAndIsCascading =
computeShouldInitiallyScheduleJobAndDependendents(cmd, forRanges); computeShouldInitiallyScheduleJobAndDependendents(cmd, forRanges);
if (!shouldSchedAndIsCascading) if (!shouldSchedAndIsCascading)
@@ -936,6 +943,15 @@ namespace driver {
collectIncrementalExternallyDependentJobsFromDependencyGraph( collectIncrementalExternallyDependentJobsFromDependencyGraph(
forRanges)) forRanges))
jobsToSchedule.insert(cmd); jobsToSchedule.insert(cmd);
// The merge-modules job is special: it *must* be scheduled if any other
// job has been scheduled because any other job can influence the
// structure of the resulting module. Additionally, the initial scheduling
// predicate above is only aware of intra-module changes. External
// dependencies changing *must* cause merge-modules to be scheduled.
if (!jobsToSchedule.empty() && mergeModulesJob) {
jobsToSchedule.insert(mergeModulesJob);
}
return jobsToSchedule; return jobsToSchedule;
} }
@@ -1031,6 +1047,13 @@ namespace driver {
/// But returns None if there was a dependency read error. /// But returns None if there was a dependency read error.
Optional<std::pair<Job::Condition, bool>> Optional<std::pair<Job::Condition, bool>>
loadDependenciesAndComputeCondition(const Job *const Cmd, bool forRanges) { loadDependenciesAndComputeCondition(const Job *const Cmd, bool forRanges) {
// merge-modules Jobs do not have .swiftdeps files associated with them,
// however, their compilation condition is computed as a function of their
// inputs, so their condition can be used as normal.
if (isa<MergeModuleJobAction>(Cmd->getSource())) {
return std::make_pair(Cmd->getCondition(), true);
}
// Try to load the dependencies file for this job. If there isn't one, we // Try to load the dependencies file for this job. If there isn't one, we
// always have to run the job, but it doesn't affect any other jobs. If // always have to run the job, but it doesn't affect any other jobs. If
// there should be one but it's not present or can't be loaded, we have to // there should be one but it's not present or can't be loaded, we have to
@@ -1163,7 +1186,12 @@ namespace driver {
continue; continue;
} }
// Can we run a cross-module incremental build at all? If not, fallback. // Is this module out of date? If not, just keep searching.
if (Comp.getLastBuildTime() >= depStatus.getLastModificationTime())
continue;
// Can we run a cross-module incremental build at all?
// If not, fall back.
if (!Comp.getEnableCrossModuleIncrementalBuild()) { if (!Comp.getEnableCrossModuleIncrementalBuild()) {
fallbackToExternalBehavior(external); fallbackToExternalBehavior(external);
continue; continue;
@@ -1609,8 +1637,8 @@ namespace driver {
CompileJobAction::InputInfo info; CompileJobAction::InputInfo info;
info.previousModTime = entry.first->getInputModTime(); info.previousModTime = entry.first->getInputModTime();
info.status = entry.second ? info.status = entry.second ?
CompileJobAction::InputInfo::NeedsCascadingBuild : CompileJobAction::InputInfo::Status::NeedsCascadingBuild :
CompileJobAction::InputInfo::NeedsNonCascadingBuild; CompileJobAction::InputInfo::Status::NeedsNonCascadingBuild;
inputs[&inputFile->getInputArg()] = info; inputs[&inputFile->getInputArg()] = info;
} }
} }
@@ -1627,7 +1655,7 @@ namespace driver {
CompileJobAction::InputInfo info; CompileJobAction::InputInfo info;
info.previousModTime = entry->getInputModTime(); info.previousModTime = entry->getInputModTime();
info.status = CompileJobAction::InputInfo::UpToDate; info.status = CompileJobAction::InputInfo::Status::UpToDate;
inputs[&inputFile->getInputArg()] = info; inputs[&inputFile->getInputArg()] = info;
} }
} }

View File

@@ -59,12 +59,12 @@ inline static StringRef getName(TopLevelKey Key) {
inline static StringRef inline static StringRef
getIdentifierForInputInfoStatus(CompileJobAction::InputInfo::Status Status) { getIdentifierForInputInfoStatus(CompileJobAction::InputInfo::Status Status) {
switch (Status) { switch (Status) {
case CompileJobAction::InputInfo::UpToDate: case CompileJobAction::InputInfo::Status::UpToDate:
return ""; return "";
case CompileJobAction::InputInfo::NewlyAdded: case CompileJobAction::InputInfo::Status::NewlyAdded:
case CompileJobAction::InputInfo::NeedsCascadingBuild: case CompileJobAction::InputInfo::Status::NeedsCascadingBuild:
return "!dirty"; return "!dirty";
case CompileJobAction::InputInfo::NeedsNonCascadingBuild: case CompileJobAction::InputInfo::Status::NeedsNonCascadingBuild:
return "!private"; return "!private";
} }
@@ -76,11 +76,11 @@ getIdentifierForInputInfoStatus(CompileJobAction::InputInfo::Status Status) {
/// compilation record file (.swiftdeps file). /// compilation record file (.swiftdeps file).
inline static Optional<CompileJobAction::InputInfo::Status> inline static Optional<CompileJobAction::InputInfo::Status>
getInfoStatusForIdentifier(StringRef Identifier) { getInfoStatusForIdentifier(StringRef Identifier) {
return llvm::StringSwitch<Optional< using InputStatus = CompileJobAction::InputInfo::Status;
CompileJobAction::InputInfo::Status>>(Identifier) return llvm::StringSwitch<Optional<InputStatus>>(Identifier)
.Case("", CompileJobAction::InputInfo::UpToDate) .Case("", InputStatus::UpToDate)
.Case("!dirty", CompileJobAction::InputInfo::NeedsCascadingBuild) .Case("!dirty", InputStatus::NeedsCascadingBuild)
.Case("!private", CompileJobAction::InputInfo::NeedsNonCascadingBuild) .Case("!private", InputStatus::NeedsNonCascadingBuild)
.Default(None); .Default(None);
} }

View File

@@ -1876,6 +1876,54 @@ Driver::computeCompilerMode(const DerivedArgList &Args,
return OutputInfo::Mode::StandardCompile; return OutputInfo::Mode::StandardCompile;
} }
namespace {
/// Encapsulates the computation of input jobs that are relevant to the
/// merge-modules job the scheduler can insert if we are not in a single compile
/// mode.
class ModuleInputs final {
private:
using InputInfo = IncrementalJobAction::InputInfo;
SmallVector<const Action *, 2> AllModuleInputs;
InputInfo StatusBound;
public:
explicit ModuleInputs()
: StatusBound
{InputInfo::Status::UpToDate, llvm::sys::TimePoint<>::min()} {}
public:
void addInput(const Action *inputAction) {
if (auto *IJA = dyn_cast<IncrementalJobAction>(inputAction)) {
// Take the upper bound of the status of any incremental inputs to
// ensure that the merge-modules job gets run if *any* input job is run.
const auto conservativeStatus =
std::max(StatusBound.status, IJA->getInputInfo().status);
// The modification time here is not important to the rest of the
// incremental build. We take the upper bound in case an attempt to
// compare the swiftmodule output's mod time and any input files is
// made. If the compilation has been correctly scheduled, the
// swiftmodule's mod time will always strictly exceed the mod time of
// any of its inputs when we are able to skip it.
const auto conservativeModTime = std::max(
StatusBound.previousModTime, IJA->getInputInfo().previousModTime);
StatusBound = InputInfo{conservativeStatus, conservativeModTime};
}
AllModuleInputs.push_back(inputAction);
}
public:
/// Returns \c true if no inputs have been registered with this instance.
bool empty() const { return AllModuleInputs.empty(); }
public:
/// Consumes this \c ModuleInputs instance and returns a merge-modules action
/// from the list of input actions and status it has computed thus far.
JobAction *intoAction(Compilation &C) && {
return C.createAction<MergeModuleJobAction>(AllModuleInputs, StatusBound);
}
};
} // namespace
void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions, void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
const ToolChain &TC, const OutputInfo &OI, const ToolChain &TC, const OutputInfo &OI,
const InputInfoMap *OutOfDateMap, const InputInfoMap *OutOfDateMap,
@@ -1888,7 +1936,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
return; return;
} }
SmallVector<const Action *, 2> AllModuleInputs; ModuleInputs AllModuleInputs;
SmallVector<const Action *, 2> AllLinkerInputs; SmallVector<const Action *, 2> AllLinkerInputs;
switch (OI.CompilerMode) { switch (OI.CompilerMode) {
@@ -1929,10 +1977,8 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
// Source inputs always need to be compiled. // Source inputs always need to be compiled.
assert(file_types::isPartOfSwiftCompilation(InputType)); assert(file_types::isPartOfSwiftCompilation(InputType));
CompileJobAction::InputInfo previousBuildState = { auto previousBuildState =
CompileJobAction::InputInfo::NeedsCascadingBuild, IncrementalJobAction::InputInfo::makeNeedsCascadingRebuild();
llvm::sys::TimePoint<>::min()
};
if (OutOfDateMap) if (OutOfDateMap)
previousBuildState = OutOfDateMap->lookup(InputArg); previousBuildState = OutOfDateMap->lookup(InputArg);
if (Args.hasArg(options::OPT_embed_bitcode)) { if (Args.hasArg(options::OPT_embed_bitcode)) {
@@ -1940,7 +1986,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
Current, file_types::TY_LLVM_BC, previousBuildState); Current, file_types::TY_LLVM_BC, previousBuildState);
if (PCH) if (PCH)
cast<JobAction>(Current)->addInput(PCH); cast<JobAction>(Current)->addInput(PCH);
AllModuleInputs.push_back(Current); AllModuleInputs.addInput(Current);
Current = C.createAction<BackendJobAction>(Current, Current = C.createAction<BackendJobAction>(Current,
OI.CompilerOutputType, 0); OI.CompilerOutputType, 0);
} else { } else {
@@ -1949,7 +1995,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
previousBuildState); previousBuildState);
if (PCH) if (PCH)
cast<JobAction>(Current)->addInput(PCH); cast<JobAction>(Current)->addInput(PCH);
AllModuleInputs.push_back(Current); AllModuleInputs.addInput(Current);
} }
AllLinkerInputs.push_back(Current); AllLinkerInputs.push_back(Current);
break; break;
@@ -1961,7 +2007,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
// When generating a .swiftmodule as a top-level output (as opposed // When generating a .swiftmodule as a top-level output (as opposed
// to, for example, linking an image), treat .swiftmodule files as // to, for example, linking an image), treat .swiftmodule files as
// inputs to a MergeModule action. // inputs to a MergeModule action.
AllModuleInputs.push_back(Current); AllModuleInputs.addInput(Current);
break; break;
} else if (OI.shouldLink()) { } else if (OI.shouldLink()) {
// Otherwise, if linking, pass .swiftmodule files as inputs to the // Otherwise, if linking, pass .swiftmodule files as inputs to the
@@ -2043,7 +2089,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
// Create a single CompileJobAction and a single BackendJobAction. // Create a single CompileJobAction and a single BackendJobAction.
JobAction *CA = JobAction *CA =
C.createAction<CompileJobAction>(file_types::TY_LLVM_BC); C.createAction<CompileJobAction>(file_types::TY_LLVM_BC);
AllModuleInputs.push_back(CA); AllModuleInputs.addInput(CA);
int InputIndex = 0; int InputIndex = 0;
for (const InputPair &Input : Inputs) { for (const InputPair &Input : Inputs) {
@@ -2079,7 +2125,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
CA->addInput(C.createAction<InputAction>(*InputArg, InputType)); CA->addInput(C.createAction<InputAction>(*InputArg, InputType));
} }
AllModuleInputs.push_back(CA); AllModuleInputs.addInput(CA);
AllLinkerInputs.push_back(CA); AllLinkerInputs.push_back(CA);
break; break;
} }
@@ -2128,7 +2174,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
!AllModuleInputs.empty()) { !AllModuleInputs.empty()) {
// We're performing multiple compilations; set up a merge module step // We're performing multiple compilations; set up a merge module step
// so we generate a single swiftmodule as output. // so we generate a single swiftmodule as output.
MergeModuleAction = C.createAction<MergeModuleJobAction>(AllModuleInputs); MergeModuleAction = std::move(AllModuleInputs).intoAction(C);
} }
bool shouldPerformLTO = OI.LTOVariant != OutputInfo::LTOKind::None; bool shouldPerformLTO = OI.LTOVariant != OutputInfo::LTOKind::None;
@@ -2703,42 +2749,49 @@ static void addDiagFileOutputForPersistentPCHAction(
/// If the file at \p input has not been modified since the last build (i.e. its /// If the file at \p input has not been modified since the last build (i.e. its
/// mtime has not changed), adjust the Job's condition accordingly. /// mtime has not changed), adjust the Job's condition accordingly.
static void static void handleCompileJobCondition(Job *J,
handleCompileJobCondition(Job *J, CompileJobAction::InputInfo inputInfo, CompileJobAction::InputInfo inputInfo,
StringRef input, bool alwaysRebuildDependents) { Optional<StringRef> input,
if (inputInfo.status == CompileJobAction::InputInfo::NewlyAdded) { bool alwaysRebuildDependents) {
using InputStatus = CompileJobAction::InputInfo::Status;
if (inputInfo.status == InputStatus::NewlyAdded) {
J->setCondition(Job::Condition::NewlyAdded); J->setCondition(Job::Condition::NewlyAdded);
return; return;
} }
auto output = J->getOutput().getPrimaryOutputFilename();
bool hasValidModTime = false; bool hasValidModTime = false;
llvm::sys::fs::file_status inputStatus; llvm::sys::fs::file_status inputStatus;
if (!llvm::sys::fs::status(input, inputStatus)) { if (input.hasValue() && !llvm::sys::fs::status(*input, inputStatus)) {
J->setInputModTime(inputStatus.getLastModificationTime());
hasValidModTime = J->getInputModTime() == inputInfo.previousModTime;
} else if (!llvm::sys::fs::status(output, inputStatus)) {
J->setInputModTime(inputStatus.getLastModificationTime()); J->setInputModTime(inputStatus.getLastModificationTime());
hasValidModTime = true; hasValidModTime = true;
} }
Job::Condition condition; Job::Condition condition;
if (hasValidModTime && J->getInputModTime() == inputInfo.previousModTime) { if (hasValidModTime) {
switch (inputInfo.status) { switch (inputInfo.status) {
case CompileJobAction::InputInfo::UpToDate: case InputStatus::UpToDate:
if (llvm::sys::fs::exists(J->getOutput().getPrimaryOutputFilename())) if (llvm::sys::fs::exists(output))
condition = Job::Condition::CheckDependencies; condition = Job::Condition::CheckDependencies;
else else
condition = Job::Condition::RunWithoutCascading; condition = Job::Condition::RunWithoutCascading;
break; break;
case CompileJobAction::InputInfo::NeedsCascadingBuild: case InputStatus::NeedsCascadingBuild:
condition = Job::Condition::Always; condition = Job::Condition::Always;
break; break;
case CompileJobAction::InputInfo::NeedsNonCascadingBuild: case InputStatus::NeedsNonCascadingBuild:
condition = Job::Condition::RunWithoutCascading; condition = Job::Condition::RunWithoutCascading;
break; break;
case CompileJobAction::InputInfo::NewlyAdded: case InputStatus::NewlyAdded:
llvm_unreachable("handled above"); llvm_unreachable("handled above");
} }
} else { } else {
if (alwaysRebuildDependents || if (alwaysRebuildDependents ||
inputInfo.status == CompileJobAction::InputInfo::NeedsCascadingBuild) { inputInfo.status == InputStatus::NeedsCascadingBuild) {
condition = Job::Condition::Always; condition = Job::Condition::Always;
} else { } else {
condition = Job::Condition::RunWithoutCascading; condition = Job::Condition::RunWithoutCascading;
@@ -2895,14 +2948,18 @@ Job *Driver::buildJobsForAction(Compilation &C, const JobAction *JA,
Job *J = C.addJob(std::move(ownedJob)); Job *J = C.addJob(std::move(ownedJob));
// If we track dependencies for this job, we may be able to avoid running it. // If we track dependencies for this job, we may be able to avoid running it.
if (!J->getOutput() if (auto incrementalJob = dyn_cast<IncrementalJobAction>(JA)) {
.getAdditionalOutputForType(file_types::TY_SwiftDeps) const bool alwaysRebuildDependents =
.empty()) { C.getArgs().hasArg(options::OPT_driver_always_rebuild_dependents);
if (InputActions.size() == 1) { if (!J->getOutput()
auto compileJob = cast<CompileJobAction>(JA); .getAdditionalOutputForType(file_types::TY_SwiftDeps)
bool alwaysRebuildDependents = .empty()) {
C.getArgs().hasArg(options::OPT_driver_always_rebuild_dependents); if (InputActions.size() == 1) {
handleCompileJobCondition(J, compileJob->getInputInfo(), BaseInput, handleCompileJobCondition(J, incrementalJob->getInputInfo(), BaseInput,
alwaysRebuildDependents);
}
} else if (isa<MergeModuleJobAction>(JA)) {
handleCompileJobCondition(J, incrementalJob->getInputInfo(), None,
alwaysRebuildDependents); alwaysRebuildDependents);
} }
} }

View File

@@ -15,4 +15,4 @@
// CHECK-SECOND-NOT: warning // CHECK-SECOND-NOT: warning
// CHECK-SECOND-NOT: Handled // CHECK-SECOND-NOT: Handled
// CHECK-SECOND: Produced master.swiftmodule // CHECK-SECOND-NOT: Produced master.swiftmodule

View File

@@ -38,4 +38,21 @@
// MODULE-B: Job finished: {merge-module: B.swiftmodule <= B.o} // MODULE-B: Job finished: {merge-module: B.swiftmodule <= B.o}
// MODULE-A: Job skipped: {compile: A.o <= A.swift} // MODULE-A: Job skipped: {compile: A.o <= A.swift}
// MODULE-A: Job finished: {merge-module: A.swiftmodule <= A.o} // MODULE-A: Job skipped: {merge-module: A.swiftmodule <= A.o}
//
// And ensure that the null build really is null.
//
// RUN: cd %t && %swiftc_driver -c -incremental -emit-dependencies -emit-module -emit-module-path %t/C.swiftmodule -enable-experimental-cross-module-incremental-build -module-name C -I %t -output-file-map %t/C.json -working-directory %t -driver-show-incremental -driver-show-job-lifecycle -DNEW %t/C.swift 2>&1 | %FileCheck -check-prefix MODULE-C-NULL %s
// RUN: cd %t && %swiftc_driver -c -incremental -emit-dependencies -emit-module -emit-module-path %t/B.swiftmodule -enable-experimental-cross-module-incremental-build -module-name B -I %t -output-file-map %t/B.json -working-directory %t -driver-show-incremental -driver-show-job-lifecycle %t/B.swift 2>&1 | %FileCheck -check-prefix MODULE-B-NULL %s
// RUN: cd %t && %swiftc_driver -c -incremental -emit-dependencies -emit-module -emit-module-path %t/A.swiftmodule -enable-experimental-cross-module-incremental-build -module-name A -I %t -output-file-map %t/A.json -working-directory %t -driver-show-incremental -driver-show-job-lifecycle %t/A.swift 2>&1 | %FileCheck -check-prefix MODULE-A-NULL %s
// MODULE-C-NULL: Job skipped: {compile: C.o <= C.swift}
// MODULE-C-NULL: Job skipped: {merge-module: C.swiftmodule <= C.o}
// MODULE-B-NULL: Job skipped: {compile: B.o <= B.swift}
// MODULE-B-NULL: Job skipped: {merge-module: B.swiftmodule <= B.o}
// MODULE-A-NULL: Job skipped: {compile: A.o <= A.swift}
// MODULE-A-NULL: Job skipped: {merge-module: A.swiftmodule <= A.o}

View File

@@ -42,3 +42,20 @@
// MODULE-A: Queuing because of incremental external dependencies: {compile: A.o <= A.swift} // MODULE-A: Queuing because of incremental external dependencies: {compile: A.o <= A.swift}
// MODULE-A: Job finished: {compile: A.o <= A.swift} // MODULE-A: Job finished: {compile: A.o <= A.swift}
// MODULE-A: Job finished: {merge-module: A.swiftmodule <= A.o} // MODULE-A: Job finished: {merge-module: A.swiftmodule <= A.o}
//
// And ensure that the null build really is null.
//
// RUN: cd %t && %swiftc_driver -c -incremental -emit-dependencies -emit-module -emit-module-path %t/C.swiftmodule -enable-experimental-cross-module-incremental-build -module-name C -I %t -output-file-map %t/C.json -working-directory %t -driver-show-incremental -driver-show-job-lifecycle -DUSEC -DNEW %t/C.swift 2>&1 | %FileCheck -check-prefix MODULE-C-NULL %s
// RUN: cd %t && %swiftc_driver -c -incremental -emit-dependencies -emit-module -emit-module-path %t/B.swiftmodule -enable-experimental-cross-module-incremental-build -module-name B -I %t -output-file-map %t/B.json -working-directory %t -driver-show-incremental -driver-show-job-lifecycle -DUSEC %t/B.swift 2>&1 | %FileCheck -check-prefix MODULE-B-NULL %s
// RUN: cd %t && %swiftc_driver -c -incremental -emit-dependencies -emit-module -emit-module-path %t/A.swiftmodule -enable-experimental-cross-module-incremental-build -module-name A -I %t -output-file-map %t/A.json -working-directory %t -driver-show-incremental -driver-show-job-lifecycle -DUSEC %t/A.swift 2>&1 | %FileCheck -check-prefix MODULE-A-NULL %s
// MODULE-C-NULL: Job skipped: {compile: C.o <= C.swift}
// MODULE-C-NULL: Job skipped: {merge-module: C.swiftmodule <= C.o}
// MODULE-B-NULL: Job skipped: {compile: B.o <= B.swift}
// MODULE-B-NULL: Job skipped: {merge-module: B.swiftmodule <= B.o}
// MODULE-A-NULL: Job skipped: {compile: A.o <= A.swift}
// MODULE-A-NULL: Job skipped: {merge-module: A.swiftmodule <= A.o}