
org.gradle.language.cpp.CppIncrementalBuildIntegrationTest.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.language.cpp
import org.gradle.integtests.fixtures.CompilationOutputsFixture
import org.gradle.integtests.fixtures.executer.GradleExecuter
import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
import org.gradle.test.fixtures.file.TestFile
import spock.lang.Unroll
class CppIncrementalBuildIntegrationTest extends AbstractInstalledToolChainIntegrationSpec implements CppTaskNames {
private static final String LIBRARY = ':library'
private static final String APP = ':app'
TestFile appSourceFile
TestFile appOtherSourceFile
TestFile appHeaderFile
TestFile libraryHeaderFile
TestFile libraryImplHeaderFile
TestFile librarySourceFile
TestFile libraryOtherSourceFile
String sourceType = "cpp"
def libObjects = new CompilationOutputsFixture(file("library/build/obj/main/debug"), [".o", ".obj"])
def appObjects = new CompilationOutputsFixture(file("app/build/obj/main/debug"), [".o", ".obj"])
def install = installation("app/build/install/main/debug")
def libraryDebug = tasks(LIBRARY).debug
def appDebug = tasks(APP).debug
def installApp = appDebug.install
def setup() {
buildFile << """
project(':library') {
apply plugin: 'cpp-library'
}
project(':app') {
apply plugin: 'cpp-application'
dependencies {
implementation project(':library')
}
}
"""
settingsFile << """
rootProject.name = 'test'
include 'library', 'app'
"""
appHeaderFile = file("app/src/main/cpp/app.hpp") << """
#include
extern void greeting(const char* name, std::string& result);
"""
appSourceFile = file("app/src/main/cpp/main.cpp") << """
#include
#include
#include "app.hpp"
using namespace std;
int main() {
string msg;
greeting("world", msg);
log(msg);
return 0;
}
"""
appOtherSourceFile = file("app/src/main/cpp/greet.cpp")
appOtherSourceFile << """
#include "app.hpp"
#define PREFIX "hello"
using namespace std;
extern void greeting(const char* name, string& result) {
result.append(PREFIX);
result.append(" ");
result.append(name);
}
"""
libraryHeaderFile = file("library/src/main/public/lib.h")
libraryHeaderFile << """
#pragma once
#include
#ifdef _WIN32
#define EXPORT_FUNC __declspec(dllexport)
#else
#define EXPORT_FUNC
#endif
void EXPORT_FUNC log(const std::string& message);
void EXPORT_FUNC log(const char* message);
"""
libraryImplHeaderFile = file("library/src/main/headers/lib_impl.h")
libraryImplHeaderFile << """
#pragma once
"""
librarySourceFile = file("library/src/main/cpp/log_string.cpp")
librarySourceFile << """
#include
#include
#include
using namespace std;
void log(const string& message) {
cout << message;
}
"""
libraryOtherSourceFile = file("library/src/main/cpp/log_chars.cpp")
libraryOtherSourceFile << """
#include
#include
#include "lib_impl.h"
using namespace std;
void log(const char* message) {
cout << message;
}
"""
}
def "rebuilds executable with single source file change"() {
given:
run installApp
libObjects.snapshot()
appObjects.snapshot()
when:
appSourceFile.replace("world", "planet")
and:
run installApp
then:
result.assertTasksExecuted(libraryDebug.allToLink, appDebug.allToInstall)
result.assertTasksSkipped(libraryDebug.allToLink)
result.assertTasksNotSkipped(appDebug.allToInstall)
and:
libObjects.noneRecompiled()
appObjects.recompiledFile(appSourceFile)
// Test assumes that the app has multiple source files and only one of them has changed. Verify that assumption
appObjects.hasFiles(appSourceFile, appOtherSourceFile)
and:
install.assertInstalled()
install.exec().out == "hello planet"
when:
run installApp
then:
nonSkippedTasks.empty
}
def "recompiles library and relinks executable after single library source file change"() {
given:
run installApp
libObjects.snapshot()
appObjects.snapshot()
when:
librarySourceFile.replace("cout << message", 'cout << "[" << message << "]"')
and:
run installApp
then:
result.assertTasksExecuted(libraryDebug.allToLink, appDebug.allToInstall)
if (toolChain.visualCpp) {
// App link may or may not be required
skipped appDebug.compile
executed libraryDebug.compile, libraryDebug.link
executed appDebug.install
} else {
result.assertTasksSkipped(appDebug.compile)
result.assertTasksNotSkipped(libraryDebug.allToLink, appDebug.link, appDebug.install)
}
and:
appObjects.noneRecompiled()
libObjects.recompiledFile(librarySourceFile)
and:
install.assertInstalled()
install.exec().out == "[hello world]"
when:
run installApp
then:
nonSkippedTasks.empty
}
def "recompiles binary and does not relink when public header file changes in a way that does not affect the object files"() {
given:
run installApp
libObjects.snapshot()
appObjects.snapshot()
when:
libraryHeaderFile << """
// Comment added to the end of the header file
"""
run installApp
then:
executedAndNotSkipped libraryDebug.compile
executedAndNotSkipped appDebug.compile
if (nonDeterministicCompilation) {
// Relinking may (or may not) be required after recompiling
executed libraryDebug.link
executed appDebug.link, installApp
} else {
skipped libraryDebug.link
skipped appDebug.link, installApp
}
and:
appObjects.recompiledFile(appSourceFile)
libObjects.recompiledFiles(librarySourceFile, libraryOtherSourceFile)
when:
run installApp
then:
nonSkippedTasks.empty
}
def "recompiles binary when implementation header file changes"() {
given:
run installApp
libObjects.snapshot()
appObjects.snapshot()
when:
libraryImplHeaderFile << """
#define UNUSED_MACRO 123
"""
run installApp
then:
executedAndNotSkipped libraryDebug.compile
skipped appDebug.compile
if (nonDeterministicCompilation) {
// Relinking may (or may not) be required after recompiling
executed libraryDebug.link
executed appDebug.link, installApp
} else {
skipped libraryDebug.link
skipped appDebug.link, installApp
}
and:
appObjects.noneRecompiled()
libObjects.recompiledFiles(librarySourceFile, libraryOtherSourceFile)
when:
run installApp
then:
nonSkippedTasks.empty
}
def "recompiles only those source files affected by a header file change"() {
given:
def greetingHeader = file("app/src/main/headers/greeting.hpp")
greetingHeader << """
#define PREFIX "hello"
"""
appOtherSourceFile.text = """
#include "app.hpp"
#include "greeting.hpp"
using namespace std;
void greeting(const char* name, string& result) {
result.append(PREFIX);
result.append(" ");
result.append(name);
}
"""
run installApp
libObjects.snapshot()
appObjects.snapshot()
when:
greetingHeader.replace("hello", "hi")
run installApp
then:
result.assertTasksSkipped(libraryDebug.allToLink)
executedAndNotSkipped appDebug.compile
and:
libObjects.noneRecompiled()
appObjects.recompiledFile(appOtherSourceFile)
// Test assumes there are multiple source files: one that includes the header and one that does not. Verify that assumption
appObjects.hasFiles(appSourceFile, appOtherSourceFile)
when:
libObjects.snapshot()
appObjects.snapshot()
run installApp
then:
nonSkippedTasks.empty
and:
libObjects.noneRecompiled()
appObjects.noneRecompiled()
}
def "considers only those headers that are reachable from source files as inputs"() {
given:
def unused = file("app/src/main/headers/ignore1.h") << "broken!"
def unusedPrivate = file("app/src/main/cpp/ignore2.h") << "broken!"
run installApp
libObjects.snapshot()
appObjects.snapshot()
when:
unused << "even more broken"
unusedPrivate << "even more broken"
file("src/main/headers/ignored3.h") << "broken"
file("src/main/headers/some-dir").mkdirs()
file("src/main/cpp/ignored4.h") << "broken"
file("src/main/cpp/some-dir").mkdirs()
run installApp
then:
nonSkippedTasks.empty
when:
unused.delete()
unusedPrivate.delete()
run installApp
then:
nonSkippedTasks.empty
when:
libraryHeaderFile << """
int unused();
"""
run installApp
then:
executedAndNotSkipped libraryDebug.compile
executedAndNotSkipped appDebug.compile
and:
appObjects.recompiledFile(appSourceFile)
libObjects.recompiledFiles(librarySourceFile, libraryOtherSourceFile)
}
def "header file referenced using relative path is considered an input"() {
given:
def unused = file("app/src/main/headers/ignore1.h") << "broken!"
appOtherSourceFile.replace('#define PREFIX "hello"', '#include "../not_included/hello.h"')
def headerFile = file("app/src/main/not_included/hello.h") << """
const char* get_hello();
#define PREFIX get_hello()
"""
def sourceFile = file("app/src/main/cpp/hello.cpp")
sourceFile.text = """
#include
#include "../not_included/hello.h"
const char* get_hello() {
return "Hi";
}
"""
run installApp
appObjects.snapshot()
libObjects.snapshot()
when:
succeeds installApp
install.exec().out == "Hi world"
then:
nonSkippedTasks.empty
when:
headerFile << "void another_thing();"
then:
succeeds installApp
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appOtherSourceFile, sourceFile)
libObjects.noneRecompiled()
and:
install.exec().out == "Hi world"
when:
unused << "broken again"
then:
succeeds installApp
install.exec().out == "Hi world"
and:
nonSkippedTasks.empty
}
@Unroll
def "header file referenced using macro #macro is considered an input"() {
when:
def unused = file("app/src/main/headers/ignore1.h") << "broken!"
file("app/src/main/headers/defs.h") << """
#define _HELLO_HEADER_2 "hello.h"
#define _HELLO_HEADER_1 _HELLO_HEADER_2
#define HELLO_HEADER MACRO_FUNCTION() // some indirection
#define MACRO_FUNCTION( ) _HELLO_HEADER_1
#define FUNCTION_RETURNS_STRING(X) "hello.h"
#define FUNCTION_RETURNS_MACRO(X) HELLO_HEADER
#define FUNCTION_RETURNS_MACRO_CALL(X) FUNCTION_RETURNS_ARG(X)
#define FUNCTION_RETURNS_ARG(X) X
#define PREFIX MACRO_USES
#define SUFFIX() _FUNCTION
#define ARGS (MACRO_FUNCTION())
// Token concatenation ## does not macro expand macro function args, so is usually wrapped by another macro function
#define CONCAT_FUNCTION2(X, Y) X ## Y
#define CONCAT_FUNCTION(X, Y) CONCAT_FUNCTION2(X, Y)
"""
def headerFile = file("app/src/main/headers/hello.h") << """
#define MESSAGE "one"
"""
appSourceFile.text = """
#include "defs.h"
#define MACRO_USES_ANOTHER_MACRO HELLO_HEADER
#define MACRO_USES_STRING_CONSTANT "hello.h"
#define MACRO_USES_SYSTEM_PATH
#define MACRO_USES_FUNCTION MACRO_FUNCTION()
#define MACRO_USES_FUNCTION_WITH_ARGS FUNCTION_RETURNS_MACRO_CALL(MACRO_USES_FUNCTION)
#define MACRO_USES_CONCAT_FUNCTION CONCAT_FUNCTION(PREFIX, SUFFIX())
#ifdef _MSC_VER // only for Visual C++
#define MACRO_PRODUCES_FUNCTION_CALL CONCAT_FUNCTION(FUNCTION_RETURNS_ARG, ARGS)
#else
#define MACRO_PRODUCES_FUNCTION_CALL "hello.h" // ignore
#endif
#include ${macro}
#include
int main () {
std::cout << MESSAGE;
return 0;
}
"""
then:
succeeds installApp
and:
assert install.exec().out == "one"
when:
succeeds installApp
appObjects.snapshot()
libObjects.snapshot()
then:
nonSkippedTasks.empty
when:
headerFile.replace('one', 'two')
succeeds installApp
then:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
and:
install.exec().out == "two"
when:
unused << "more broken"
succeeds installApp
then:
nonSkippedTasks.empty
where:
macro << [
"MACRO_USES_STRING_CONSTANT",
"MACRO_USES_SYSTEM_PATH",
"MACRO_USES_ANOTHER_MACRO",
"MACRO_USES_FUNCTION",
"MACRO_USES_FUNCTION_WITH_ARGS",
"MACRO_USES_CONCAT_FUNCTION",
"MACRO_PRODUCES_FUNCTION_CALL",
"MACRO_FUNCTION()",
"FUNCTION_RETURNS_STRING(ignore)",
"FUNCTION_RETURNS_MACRO(ignore)",
"FUNCTION_RETURNS_ARG(HELLO_HEADER)"
]
}
@Unroll
def "header file referenced using external macro #macro is considered an input"() {
when:
def unused = file("app/src/main/headers/ignore1.h") << "broken!"
file("app/src/main/headers/defs.h") << """
#define HEADER "hello.h"
#define HEADER_FUNC() "hello.h"
"""
def headerFile = file("app/src/main/headers/hello.h") << """
#define MESSAGE "one"
"""
appSourceFile.text = """
#include "defs.h"
#include MACRO
#include
int main () {
std::cout << MESSAGE;
return 0;
}
"""
buildFile << """
project(':app') {
tasks.withType(CppCompile) {
macros.put('MACRO','${macro}')
}
}
"""
then:
succeeds installApp
and:
assert install.exec().out == "one"
when:
succeeds installApp
appObjects.snapshot()
libObjects.snapshot()
then:
nonSkippedTasks.empty
when:
headerFile.replace('one', 'two')
succeeds installApp
then:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
and:
install.exec().out == "two"
when:
unused << "more broken"
succeeds installApp
then:
nonSkippedTasks.empty
where:
macro << [
'"hello.h"',
'',
'HEADER',
'HEADER_FUNC()'
]
}
@Unroll
def "considers all header files as input to source file with complex macro include #include"() {
when:
appSourceFile.text = """
$text
#include
int main () {
std::cout << GREETING;
return 0;
}
"""
def headerFile = file("app/src/main/headers/ignore.h") << """
IGNORE ME
"""
file("app/src/main/headers/hello.h") << """
#define GREETING "hello"
"""
then:
executer.withArgument("-i")
succeeds installApp
output.contains("Cannot locate header file for '#include $include' in source file 'main.cpp'. Assuming changed.")
install.exec().out == "hello"
// Test assumes there are 2 source files: one with unresolvable macros and one without. Verify that assumption
appObjects.hasFiles(appSourceFile, appOtherSourceFile)
when:
headerFile.text = "changed"
appObjects.snapshot()
libObjects.snapshot()
then:
executer.withArgument("-i")
succeeds installApp
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
unresolvedHeadersDetected(appDebug.compile)
when:
headerFile.delete()
appObjects.snapshot()
libObjects.snapshot()
then:
executer.withArgument("-i")
succeeds installApp
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
unresolvedHeadersDetected(appDebug.compile)
when:
succeeds installApp
then:
nonSkippedTasks.empty
when:
file("app/src/main/headers/some-dir").mkdirs()
succeeds installApp
then:
nonSkippedTasks.empty
when:
file("app/src/main/headers/some-dir").deleteDir()
succeeds installApp
then:
nonSkippedTasks.empty
when:
disableTransitiveUnresolvedHeaderDetection()
headerFile.text = "changed again"
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
and:
appObjects.noneRecompiled()
libObjects.noneRecompiled()
and:
skipped appDebug.compile
skipped libraryDebug.compile
where:
include | text
'HELLO' | '''
#define _HELLO(X) #X
#define HELLO _HELLO(hello.h)
#include HELLO
'''
'_HELLO(hello . h)' | '''
#define _HELLO(X) #X
#include _HELLO(hello.h)
'''
'MISSING' | '''
#ifdef MISSING
#include MISSING
#else
#include "hello.h"
#endif
'''
'GARBAGE' | '''
#if 0
#define GARBAGE a b c
#include GARBAGE
#else
#include "hello.h"
#endif
'''
'a b c' | '''
#if 0
#include a b c
#else
#include "hello.h"
#endif
'''
}
def "does not consider all header files as inputs if complex macro include is found in dependency and special flag is active"() {
when:
appSourceFile.text = """
#include "headers.h"
#include
int main () {
std::cout << GREETING;
return 0;
}
"""
file("app/src/main/headers/headers.h") << """
#define _HELLO(X) #X
#define HELLO _HELLO(hello.h)
#include HELLO
"""
file("app/src/main/headers/hello.h") << """
#define GREETING "hello"
"""
def headerFile = file("app/src/main/headers/ignore.h") << """
IGNORE ME
"""
then:
succeeds installApp
install.exec().out == "hello"
when:
appObjects.snapshot()
libObjects.snapshot()
and:
headerFile.text = "changed 1"
then:
succeeds installApp, '--info'
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
unresolvedHeadersDetected(appDebug.compile)
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
disableTransitiveUnresolvedHeaderDetection()
and:
appObjects.snapshot()
libObjects.snapshot()
and:
headerFile.text = "changed 3"
then:
succeeds installApp
and:
nonSkippedTasks.empty
and:
appObjects.noneRecompiled()
libObjects.noneRecompiled()
}
private GradleExecuter disableTransitiveUnresolvedHeaderDetection() {
executer.beforeExecute {
withArgument("-Dorg.gradle.internal.native.headers.unresolved.dependencies.ignore=true")
}
return executer
}
def "can have a cycle between header files"() {
def header1 = file("app/src/main/headers/hello.h")
def header2 = file("app/src/main/headers/other.h")
when:
header1 << """
#ifndef HELLO
#define HELLO
#include "other.h"
#endif
"""
header2 << """
#ifndef OTHER
#define OTHER
#define MESSAGE "hello"
#include "hello.h"
#endif
"""
appSourceFile.text = """
#include
#include "hello.h"
int main () {
std::cout << MESSAGE;
return 0;
}
"""
then:
succeeds installApp
install.exec().out == "hello"
when:
succeeds installApp
then:
nonSkippedTasks.empty
when:
appObjects.snapshot()
libObjects.snapshot()
header1 << """// some extra stuff"""
then:
succeeds installApp
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
succeeds installApp
then:
nonSkippedTasks.empty
}
def "can reference a missing header file"() {
def header = file("app/src/main/headers/hello.h")
when:
header << """
#pragma once
#define MESSAGE "hello"
#if 0
#include "missing.h"
#endif
"""
appSourceFile.text = """
#include
#include "hello.h"
int main () {
std::cout << MESSAGE;
return 0;
}
"""
then:
succeeds installApp
install.exec().out == "hello"
when:
succeeds installApp
then:
nonSkippedTasks.empty
when:
header << """// some extra stuff"""
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
succeeds installApp
then:
nonSkippedTasks.empty
}
def "source file can reference multiple header files using the same macro"() {
def header1 = file("app/src/main/headers/hello1.h")
def header2 = file("app/src/main/headers/hello2.h")
def header3 = file("app/src/main/headers/hello3.h")
def unused = file("app/src/main/headers/ignoreme.h")
when:
file("app/src/main/headers/hello.h") << """
#if 0
#include "def1.h"
#else
#include "def2.h"
#endif
#include HEADER
"""
file("app/src/main/headers/def1.h") << """
#define HEADER "hello1.h"
"""
file("app/src/main/headers/def2.h") << """
#define _HEADER "hello2.h"
#ifndef _HEADER
#define _HEADER "hello3.h"
#endif
#define HEADER _HEADER
"""
header1 << """
#define MESSAGE "one"
"""
header2 << """
#define MESSAGE "two"
"""
header3 << """
#define MESSAGE "three"
"""
unused << "broken"
appSourceFile.text = """
#include
#include "hello.h"
int main () {
std::cout << MESSAGE;
return 0;
}
"""
then:
succeeds installApp
install.exec().out == "two"
when:
succeeds installApp
then:
nonSkippedTasks.empty
when:
header2 << """// some extra stuff"""
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
succeeds installApp
then:
nonSkippedTasks.empty
when:
header3 << """// some extra stuff"""
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
succeeds installApp
then:
nonSkippedTasks.empty
when:
header1 << """// some extra stuff"""
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
unused << "more broken"
succeeds installApp
then:
nonSkippedTasks.empty
}
def "changes to the included header graph are reflected in the inputs"() {
def header = file("app/src/main/headers/hello.h")
def header1 = file("app/src/main/headers/hello1.h")
def header2 = file("app/src/main/headers/hello2.h")
when:
header << """
#pragma once
#include "hello1.h"
"""
header1 << """
#define MESSAGE "one"
"""
header2 << """
#define MESSAGE "two"
"""
appSourceFile.text = """
#include
#include "hello.h"
int main () {
std::cout << MESSAGE;
return 0;
}
"""
then:
succeeds installApp
install.exec().out == "one"
when:
header2 << " // changes"
then:
succeeds installApp
nonSkippedTasks.empty
when:
header.replace('"hello1.h"', '"hello2.h"')
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
install.exec().out == "two"
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
header1 << " // changes"
succeeds installApp
then:
nonSkippedTasks.empty
}
def "shared header can reference project specific header"() {
when:
appSourceFile.replace("log(msg)", "log_info(msg)")
libraryHeaderFile << """
#include
"""
def appDefsHeader = file("app/src/main/headers/local_defs.h")
appDefsHeader << """
#pragma once
#include
#define log_info(msg) log(std::string("[info] ") + msg)
"""
librarySourceFile.replace("cout << message", "cout << PREFIX << message")
def libDefsHeader = file("library/src/main/headers/local_defs.h")
libDefsHeader << """
#define PREFIX "LOG: "
"""
then:
succeeds installApp
install.exec().out == "LOG: [info] hello world"
when:
succeeds installApp
then:
nonSkippedTasks.empty
when:
libDefsHeader.replace('PREFIX "LOG: "', 'PREFIX "* "')
appObjects.snapshot()
libObjects.snapshot()
and:
succeeds installApp
then:
install.exec().out == "* [info] hello world"
and:
skipped appDebug.compile
executedAndNotSkipped libraryDebug.compile
and:
appObjects.noneRecompiled()
libObjects.recompiledFiles(librarySourceFile, libraryOtherSourceFile)
when:
appDefsHeader.replace('"[info]', '"INFO:')
appObjects.snapshot()
libObjects.snapshot()
and:
succeeds installApp
then:
install.exec().out == "* INFO: hello world"
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
}
def "shared header can reference source file specific header using macro include"() {
when:
libraryHeaderFile << """
#include LOCAL_DEFS
"""
appSourceFile.insertBefore("#include ", "#define LOCAL_DEFS ")
appSourceFile.replace("log(msg)", "log_info(msg)")
def appDefsHeader = file("app/src/main/headers/app_defs.h")
appDefsHeader << """
#pragma once
#include
#define log_info(msg) log(std::string("[info] ") + msg)
"""
librarySourceFile.insertBefore("#include ", "#define LOCAL_DEFS ")
librarySourceFile.replace("cout << message", "cout << PREFIX << message")
def libDefsHeader1 = file("library/src/main/headers/lib_str_defs.h")
libDefsHeader1 << """
#pragma once
#define PREFIX "LOG: "
"""
libraryOtherSourceFile.insertBefore("#include ", "#define LOCAL_DEFS ")
def libDefsHeader2 = file("library/src/main/headers/lib_char_defs.h")
libDefsHeader2 << """
#pragma once
#define PREFIX "LOG: "
"""
then:
succeeds installApp
install.exec().out == "LOG: [info] hello world"
when:
succeeds installApp
then:
nonSkippedTasks.empty
when:
libDefsHeader1.replace('PREFIX "LOG: "', 'PREFIX "* "')
appObjects.snapshot()
libObjects.snapshot()
and:
succeeds installApp
then:
install.exec().out == "* [info] hello world"
and:
skipped appDebug.compile
executedAndNotSkipped libraryDebug.compile
and:
appObjects.noneRecompiled()
libObjects.recompiledFiles(librarySourceFile)
when:
appDefsHeader.replace('"[info]', '"INFO:')
appObjects.snapshot()
libObjects.snapshot()
and:
succeeds installApp
then:
install.exec().out == "* INFO: hello world"
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
}
def "project specific header can shadow shared header"() {
when:
appSourceFile.insertBefore('#include ', '#include "common.h"')
appSourceFile.replace('"world"', 'TARGET')
def appHeaderInSrcDir = file("app/src/main/cpp/common.h")
appHeaderInSrcDir << """
#pragma once
#define TARGET "everyone"
"""
def appHeaderInHeaderDir = file("app/src/main/headers/common.h")
appHeaderInHeaderDir << """
// empty
"""
def commonHeader = file("library/src/main/public/common.h")
commonHeader << """
// empty
"""
librarySourceFile.insertBefore('#include ', '#include ')
then:
succeeds installApp
install.exec().out == "hello everyone"
when:
appHeaderInHeaderDir.text = "// ignore"
then:
succeeds installApp
and:
nonSkippedTasks.empty
when:
appHeaderInSrcDir.replace('"everyone"', '"world"')
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
install.exec().out == "hello world"
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
commonHeader.text = "// changed"
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
install.exec().out == "hello world"
and:
skipped appDebug.compile
executedAndNotSkipped libraryDebug.compile
and:
appObjects.noneRecompiled()
libObjects.recompiledFiles(librarySourceFile)
when:
commonHeader.text = appHeaderInSrcDir.text
appHeaderInSrcDir.delete()
appHeaderInHeaderDir.delete()
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
install.exec().out == "hello world"
and:
skipped appDebug.compile
executedAndNotSkipped libraryDebug.compile
and:
appObjects.noneRecompiled()
libObjects.recompiledFiles(librarySourceFile)
when:
appHeaderInHeaderDir.text = commonHeader.text
then:
succeeds installApp
nonSkippedTasks.empty
when:
appHeaderInSrcDir.text = appHeaderInHeaderDir.text
appHeaderInSrcDir.replace('"world"', '"planet"')
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
install.exec().out == "hello planet"
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
}
def "recompiles when include path changes resolve different headers"() {
when:
appSourceFile.insertBefore('#include ', '#include ')
appSourceFile.replace('"world"', 'TARGET')
def appHeaderInHeaderDir = file("app/src/main/headers/common.h")
appHeaderInHeaderDir << """
#pragma once
#define TARGET "world"
"""
def appHeaderInOtherDir = file("app/src/main/include/common.h")
appHeaderInOtherDir.text = appHeaderInHeaderDir.text
then:
succeeds installApp
install.exec().out == "hello world"
when:
buildFile << """
project(':app') {
application.privateHeaders.from = ['src/main/include']
}
"""
then:
succeeds installApp
install.exec().out == "hello world"
and:
nonSkippedTasks.empty
when:
appHeaderInHeaderDir << """
// changed
"""
then:
succeeds installApp
install.exec().out == "hello world"
and:
nonSkippedTasks.empty
when:
appHeaderInOtherDir.replace('"world"', '"universe"')
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
install.exec().out == "hello universe"
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
buildFile << """
project(':app') {
application.privateHeaders.from = ['src/main/headers']
}
"""
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
install.exec().out == "hello world"
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
}
def "recompiles when system headers change"() {
when:
appSourceFile.insertBefore('#include ', '#include ')
appSourceFile.replace('"world"', 'TARGET')
def systemHeaderInOtherDir = file("app/src/main/system/common.h")
systemHeaderInOtherDir << """
#pragma once
#define TARGET "world"
"""
buildFile << """
project(':app') {
tasks.withType(CppCompile) {
systemIncludes.from("src/main/system")
}
}
"""
then:
succeeds installApp
install.exec().out == "hello world"
when:
succeeds installApp
then:
install.exec().out == "hello world"
and:
nonSkippedTasks.empty
when:
systemHeaderInOtherDir.replace('"world"', '"universe"')
and:
appObjects.snapshot()
libObjects.snapshot()
then:
succeeds installApp
install.exec().out == "hello universe"
and:
executedAndNotSkipped appDebug.compile
skipped libraryDebug.compile
and:
appObjects.recompiledFiles(appSourceFile)
libObjects.noneRecompiled()
when:
succeeds installApp
then:
install.exec().out == "hello universe"
and:
nonSkippedTasks.empty
}
private boolean unresolvedHeadersDetected(String taskPath) {
executed(taskPath)
output.contains("After parsing the source files, Gradle cannot calculate the exact set of include files for '${taskPath}'. Every file in the include search path will be considered a header dependency.")
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy