aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.mailmap28
-rw-r--r--README34
-rw-r--r--VERSION2
-rw-r--r--doc/doc.pri2
-rw-r--r--doc/doc.qbs6
-rw-r--r--doc/man/man.pri5
-rw-r--r--doc/man/man.qbs66
-rw-r--r--doc/man/qbs.179
-rw-r--r--doc/man/see-also.h2m11
-rw-r--r--doc/qbs.qdoc342
-rw-r--r--doc/reference/items/language/depends.qdoc4
-rw-r--r--doc/reference/items/language/filetagger.qdoc26
-rw-r--r--doc/reference/items/language/group.qdoc125
-rw-r--r--doc/reference/items/language/product.qdoc2
-rw-r--r--doc/reference/items/language/profile.qdoc95
-rw-r--r--doc/reference/items/language/project.qdoc2
-rw-r--r--doc/reference/jsextensions/jsextension-fileinfo.qdoc7
-rw-r--r--doc/reference/jsextensions/jsextension-textfile.qdoc10
-rw-r--r--doc/reference/modules/android-ndk-module.qdoc3
-rw-r--r--doc/reference/modules/android-sdk-module.qdoc30
-rw-r--r--doc/reference/modules/cpp-module.qdoc80
-rw-r--r--doc/reference/modules/cpufeatures-module.qdoc191
-rw-r--r--doc/reference/modules/java-module.qdoc2
-rw-r--r--doc/reference/modules/qbs-module.qdoc6
-rw-r--r--doc/reference/modules/qt-modules.qdoc68
-rw-r--r--doc/reference/modules/vcs-module.qdoc99
-rw-r--r--docker/docker.qbs35
-rw-r--r--examples/app-and-lib/app/app.qbs23
-rw-r--r--examples/app-and-lib/app/main.cpp4
-rw-r--r--examples/app-and-lib/app_and_lib.qbs23
-rw-r--r--examples/app-and-lib/lib/lib.cpp4
-rw-r--r--examples/app-and-lib/lib/lib.h53
-rw-r--r--examples/app-and-lib/lib/lib.qbs23
-rw-r--r--examples/cocoa-application/CocoaApplication.qbs23
-rw-r--r--examples/cocoa-application/CocoaApplication/AppDelegate.h53
-rw-r--r--examples/cocoa-application/app.qbs28
-rw-r--r--examples/cocoa-application/dmg.qbs21
-rw-r--r--examples/cocoa-touch-application/CocoaTouchApplication.qbs30
-rw-r--r--examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h53
-rw-r--r--examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h53
-rw-r--r--examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h53
-rw-r--r--examples/code-generator/code-generator.qbs23
-rw-r--r--examples/code-generator/hwgen.cpp4
-rw-r--r--examples/collidingmice/collidingmice.qbs24
-rw-r--r--examples/collidingmice/main.cpp4
-rw-r--r--examples/collidingmice/mouse.cpp4
-rw-r--r--examples/collidingmice/mouse.h51
-rw-r--r--examples/compiled-qml/MainForm.ui.qml68
-rw-r--r--examples/compiled-qml/cheese.jpgbin0 -> 3029 bytes
-rw-r--r--examples/compiled-qml/main.cpp62
-rw-r--r--examples/compiled-qml/main.qml62
-rw-r--r--examples/compiled-qml/myapp.qbs66
-rw-r--r--examples/compiled-qml/qml.qrc7
-rw-r--r--examples/examples.qbs24
-rw-r--r--examples/helloworld-complex/hello.qbs23
-rw-r--r--examples/helloworld-complex/src/foo.cpp4
-rw-r--r--examples/helloworld-complex/src/foo.h53
-rw-r--r--examples/helloworld-complex/src/main.cpp4
-rw-r--r--examples/helloworld-complex/src/specialfeature.cpp4
-rw-r--r--examples/helloworld-complex/src/specialfeature.h53
-rw-r--r--examples/helloworld-minimal/hello.qbs23
-rw-r--r--examples/helloworld-minimal/main.cpp4
-rw-r--r--examples/helloworld-qt/hello.qbs23
-rw-r--r--examples/helloworld-qt/main.cpp4
-rw-r--r--examples/install-bundle/coreutils.cpp4
-rw-r--r--examples/install-bundle/coreutils.h53
-rw-r--r--examples/install-bundle/main.cpp4
-rw-r--r--qbs-resources/modules/docker/docker.qbs51
-rw-r--r--qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs1
-rw-r--r--qbs.qbs5
-rw-r--r--scripts/scripts.qbs9
-rwxr-xr-xscripts/update-dmgbuild.sh49
-rwxr-xr-xscripts/update-xcspecs.sh (renamed from share/qbs/modules/bundle/update-specs.sh)5
-rw-r--r--share/qbs/imports/qbs/PathTools/path-tools.js13
-rw-r--r--share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs35
-rw-r--r--share/qbs/imports/qbs/Probes/GccProbe.qbs38
-rw-r--r--share/qbs/imports/qbs/Probes/JdkProbe.qbs11
-rw-r--r--share/qbs/imports/qbs/Probes/JdkVersionProbe.qbs45
-rw-r--r--share/qbs/imports/qbs/Probes/MsvcProbe.qbs25
-rw-r--r--share/qbs/imports/qbs/WindowsUtils/windows-utils.js21
-rw-r--r--share/qbs/imports/qbs/base/AndroidApk.qbs11
-rw-r--r--share/qbs/modules/Android/sdk/sdk.qbs126
-rw-r--r--share/qbs/modules/Android/sdk/utils.js115
-rw-r--r--share/qbs/modules/cpp/CppModule.qbs18
-rw-r--r--share/qbs/modules/cpp/DarwinGCC.qbs13
-rw-r--r--share/qbs/modules/cpp/GenericGCC.qbs5
-rw-r--r--share/qbs/modules/cpp/darwin.js11
-rw-r--r--share/qbs/modules/cpp/gcc.js159
-rw-r--r--share/qbs/modules/cpp/msvc.js43
-rw-r--r--share/qbs/modules/cpp/windows-mingw.qbs10
-rw-r--r--share/qbs/modules/cpp/windows-msvc.qbs25
-rw-r--r--share/qbs/modules/cpufeatures/cpufeatures.qbs25
-rw-r--r--share/qbs/modules/java/JavaModule.qbs17
-rw-r--r--share/qbs/modules/vcs/vcs-module.qbs153
-rwxr-xr-xsrc/3rdparty/python/update.sh7
-rw-r--r--src/app/qbs-setup-qt/qbs-setup-qt.qbs2
-rw-r--r--src/app/qbs-setup-qt/setupqt.cpp11
-rw-r--r--src/app/qbs-setup-toolchains/probe.cpp15
-rw-r--r--src/app/qbs/commandlinefrontend.cpp19
-rw-r--r--src/app/qbs/commandlinefrontend.h1
-rw-r--r--src/app/qbs/parser/commandlineoption.cpp14
-rw-r--r--src/app/qbs/parser/commandlineoption.h8
-rw-r--r--src/app/qbs/parser/commandlineoptionpool.cpp8
-rw-r--r--src/app/qbs/parser/commandlineoptionpool.h1
-rw-r--r--src/app/qbs/parser/commandlineparser.cpp10
-rw-r--r--src/app/qbs/parser/commandlineparser.h1
-rw-r--r--src/app/qbs/parser/commandpool.cpp3
-rw-r--r--src/app/qbs/parser/commandtype.h2
-rw-r--r--src/app/qbs/parser/parsercommand.cpp59
-rw-r--r--src/app/qbs/parser/parsercommand.h14
-rw-r--r--src/lib/corelib/api/internaljobs.cpp12
-rw-r--r--src/lib/corelib/api/jobs.cpp3
-rw-r--r--src/lib/corelib/api/project.cpp17
-rw-r--r--src/lib/corelib/api/project.h1
-rw-r--r--src/lib/corelib/api/projectdata.cpp11
-rw-r--r--src/lib/corelib/api/projectdata.h1
-rw-r--r--src/lib/corelib/buildgraph/artifactcleaner.cpp2
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.cpp91
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.h11
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.cpp143
-rw-r--r--src/lib/corelib/buildgraph/depscanner.cpp4
-rw-r--r--src/lib/corelib/buildgraph/depscanner.h4
-rw-r--r--src/lib/corelib/buildgraph/executor.cpp231
-rw-r--r--src/lib/corelib/buildgraph/executor.h2
-rw-r--r--src/lib/corelib/buildgraph/inputartifactscanner.cpp65
-rw-r--r--src/lib/corelib/buildgraph/jscommandexecutor.cpp1
-rw-r--r--src/lib/corelib/buildgraph/processcommandexecutor.cpp30
-rw-r--r--src/lib/corelib/buildgraph/projectbuilddata.cpp84
-rw-r--r--src/lib/corelib/buildgraph/qtmocscanner.cpp35
-rw-r--r--src/lib/corelib/buildgraph/qtmocscanner.h5
-rw-r--r--src/lib/corelib/buildgraph/rawscanresults.cpp3
-rw-r--r--src/lib/corelib/buildgraph/rescuableartifactdata.h2
-rw-r--r--src/lib/corelib/buildgraph/rulegraph.cpp2
-rw-r--r--src/lib/corelib/buildgraph/rulegraph.h2
-rw-r--r--src/lib/corelib/buildgraph/rulenode.cpp22
-rw-r--r--src/lib/corelib/buildgraph/rulesapplicator.cpp34
-rw-r--r--src/lib/corelib/buildgraph/timestampsupdater.cpp2
-rw-r--r--src/lib/corelib/corelib.qbs4
-rw-r--r--src/lib/corelib/generators/generatordata.cpp75
-rw-r--r--src/lib/corelib/generators/generatordata.h112
-rw-r--r--src/lib/corelib/jsextensions/environmentextension.cpp6
-rw-r--r--src/lib/corelib/jsextensions/fileinfoextension.cpp13
-rw-r--r--src/lib/corelib/jsextensions/process.cpp10
-rw-r--r--src/lib/corelib/jsextensions/textfile.cpp55
-rw-r--r--src/lib/corelib/jsextensions/utilitiesextension.cpp18
-rw-r--r--src/lib/corelib/language/artifactproperties.cpp13
-rw-r--r--src/lib/corelib/language/artifactproperties.h4
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp22
-rw-r--r--src/lib/corelib/language/builtindeclarations.h1
-rw-r--r--src/lib/corelib/language/evaluator.cpp28
-rw-r--r--src/lib/corelib/language/evaluator.h6
-rw-r--r--[-rwxr-xr-x]src/lib/corelib/language/evaluatorscriptclass.cpp27
-rwxr-xr-xsrc/lib/corelib/language/evaluatorscriptclass.h4
-rw-r--r--src/lib/corelib/language/filecontextbase.cpp8
-rw-r--r--src/lib/corelib/language/filecontextbase.h5
-rw-r--r--src/lib/corelib/language/filetags.cpp6
-rw-r--r--src/lib/corelib/language/filetags.h1
-rw-r--r--src/lib/corelib/language/item.cpp20
-rw-r--r--src/lib/corelib/language/item.h3
-rw-r--r--src/lib/corelib/language/itemreader.cpp35
-rw-r--r--src/lib/corelib/language/itemreader.h7
-rw-r--r--src/lib/corelib/language/itemtype.h1
-rw-r--r--src/lib/corelib/language/language.cpp68
-rw-r--r--src/lib/corelib/language/language.h36
-rw-r--r--[-rwxr-xr-x]src/lib/corelib/language/moduleloader.cpp794
-rw-r--r--src/lib/corelib/language/moduleloader.h54
-rw-r--r--src/lib/corelib/language/projectresolver.cpp177
-rw-r--r--src/lib/corelib/language/projectresolver.h5
-rw-r--r--src/lib/corelib/language/scriptengine.cpp26
-rw-r--r--src/lib/corelib/language/scriptengine.h23
-rw-r--r--src/lib/corelib/language/value.h3
-rw-r--r--src/lib/corelib/logging/categories.cpp55
-rw-r--r--src/lib/corelib/logging/categories.h60
-rw-r--r--src/lib/corelib/logging/logger.h1
-rw-r--r--src/lib/corelib/logging/logging.pri2
-rw-r--r--src/lib/corelib/tools/executablefinder.cpp14
-rw-r--r--src/lib/corelib/tools/executablefinder.h5
-rw-r--r--src/lib/corelib/tools/filesaver.cpp43
-rw-r--r--src/lib/corelib/tools/filesaver.h23
-rw-r--r--src/lib/corelib/tools/hostosinfo.h31
-rw-r--r--src/lib/corelib/tools/iosutils.h95
-rw-r--r--src/lib/corelib/tools/msvcinfo.cpp24
-rw-r--r--src/lib/corelib/tools/msvcinfo.h8
-rw-r--r--src/lib/corelib/tools/persistence.cpp2
-rw-r--r--src/lib/corelib/tools/persistence.h14
-rw-r--r--src/lib/corelib/tools/profile.cpp22
-rw-r--r--src/lib/corelib/tools/profile.h4
-rw-r--r--src/lib/corelib/tools/qbspluginmanager.cpp8
-rw-r--r--src/lib/corelib/tools/set.h7
-rw-r--r--src/lib/corelib/tools/settings.cpp4
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.cpp21
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.h4
-rw-r--r--src/lib/corelib/tools/shellutils.cpp36
-rw-r--r--src/lib/corelib/tools/shellutils.h6
-rw-r--r--src/lib/corelib/tools/stlutils.h14
-rw-r--r--src/lib/corelib/tools/stringutils.h135
-rw-r--r--src/lib/corelib/tools/tools.pri2
-rw-r--r--src/lib/qtprofilesetup/qtenvironment.h3
-rw-r--r--src/lib/qtprofilesetup/qtmoduleinfo.cpp6
-rw-r--r--src/lib/qtprofilesetup/qtprofilesetup.cpp77
-rw-r--r--src/lib/qtprofilesetup/templates.qrc3
-rw-r--r--src/lib/qtprofilesetup/templates/core.qbs11
-rw-r--r--src/lib/qtprofilesetup/templates/moc.js10
-rw-r--r--src/lib/qtprofilesetup/templates/qml.qbs13
-rw-r--r--src/lib/qtprofilesetup/templates/qmlcache.qbs67
-rw-r--r--src/lib/qtprofilesetup/templates/quick.js84
-rw-r--r--src/lib/qtprofilesetup/templates/quick.qbs190
-rw-r--r--src/libexec/qbs_processlauncher/qbs_processlauncher.qbs5
-rw-r--r--src/packages/archive/archive.qbs99
-rw-r--r--src/packages/archive/qt.conf2
-rw-r--r--src/packages/packages.qbs9
-rw-r--r--src/plugins/generator/visualstudio/io/msbuildprojectwriter.cpp13
-rw-r--r--src/plugins/generator/visualstudio/io/msbuildprojectwriter.h6
-rw-r--r--src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.cpp104
-rw-r--r--src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.h15
-rw-r--r--src/plugins/generator/visualstudio/visualstudio.pro1
-rw-r--r--src/plugins/generator/visualstudio/visualstudio.qbs2
-rw-r--r--src/plugins/generator/visualstudio/visualstudiogenerator.cpp13
-rw-r--r--src/plugins/generator/visualstudio/visualstudioguidpool.cpp60
-rw-r--r--src/plugins/generator/visualstudio/visualstudioguidpool.h10
-rw-r--r--src/shared/json/README.md2
-rw-r--r--src/shared/json/json.cpp4964
-rw-r--r--src/shared/json/json.h589
-rw-r--r--src/shared/json/json.pri3
-rw-r--r--src/shared/json/json.qbs16
-rw-r--r--src/src.qbs3
-rw-r--r--tests/auto/api/testdata/local-profiles/local-profiles.qbs45
-rw-r--r--tests/auto/api/tst_api.cpp120
-rw-r--r--tests/auto/api/tst_api.h2
-rw-r--r--tests/auto/auto.pro1
-rw-r--r--tests/auto/auto.qbs1
-rw-r--r--tests/auto/blackbox/blackbox-android.pro18
-rw-r--r--tests/auto/blackbox/blackbox-android.qbs22
-rw-r--r--tests/auto/blackbox/blackbox.qbs6
-rw-r--r--tests/auto/blackbox/find/find-android.qbs1
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/multiple-apks-per-project.qbs (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/multiple-apks-per-project.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/product1.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/AndroidManifest.xml (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/AndroidManifest.xml)6
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy1/Dummy.java19
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib1.cpp (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/jni/lib1.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib2.cpp (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/jni/lib2.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/res/values/strings.xml (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/res/values/strings.xml)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/product2.qbs (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/product2.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/AndroidManifest.xml (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/AndroidManifest.xml)6
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy2/Dummy.java17
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib1.cpp (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/jni/lib1.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib2.cpp (renamed from tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/jni/lib2.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/io/qbs/lib3/lib3.java6
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib4.java2
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib5.java2
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib6.java2
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib7.java2
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib8.java2
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/multiple-libs-per-apk.qbs69
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/AndroidManifest.xml (renamed from tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/AndroidManifest.xml)6
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java19
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib1.cpp (renamed from tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/jni/lib1.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib2.cpp (renamed from tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/jni/lib2.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/res/values/strings.xml (renamed from tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/res/values/strings.xml)0
-rw-r--r--tests/auto/blackbox/testdata-android/no-native/no-native.qbs (renamed from tests/auto/blackbox/testdata-java/android/no-native/no-native.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-android/teapot/teapot.qbs (renamed from tests/auto/blackbox/testdata-java/android/teapot/teapot.qbs)38
-rw-r--r--tests/auto/blackbox/testdata-apple/apple-multiconfig/apple-multiconfig.qbs18
-rw-r--r--tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy/Dummy.java11
-rw-r--r--tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy/Dummy.java11
-rw-r--r--tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/multiple-libs-per-apk.qbs27
-rw-r--r--tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java11
-rw-r--r--tests/auto/blackbox/testdata-qt/cached-qml/MainForm.ui.qml29
-rw-r--r--tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs30
-rw-r--r--tests/auto/blackbox/testdata-qt/cached-qml/main.cpp14
-rw-r--r--tests/auto/blackbox/testdata-qt/cached-qml/main.qml17
-rw-r--r--tests/auto/blackbox/testdata-qt/cached-qml/qml.qrc7
-rw-r--r--tests/auto/blackbox/testdata-qt/cached-qml/stuff.js1
-rw-r--r--tests/auto/blackbox/testdata-qt/pkgconfig/main.cpp (renamed from tests/manual/pkgconfig/main.cpp)10
-rw-r--r--tests/auto/blackbox/testdata-qt/pkgconfig/pkgconfig.qbs (renamed from tests/manual/pkgconfig/pkgconfig.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.cpp10
-rw-r--r--tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.h13
-rw-r--r--tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/remove-moc-header-from-file-list.qbs9
-rw-r--r--tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/add-filetag-to-generated-artifact.qbs34
-rw-r--r--tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/main.cpp1
-rw-r--r--tests/auto/blackbox/testdata/compilerDefinesByLanguage/CppDefinesApp.qbs24
-rw-r--r--tests/auto/blackbox/testdata/compilerDefinesByLanguage/app.c4
-rw-r--r--tests/auto/blackbox/testdata/compilerDefinesByLanguage/compilerDefinesByLanguage.qbs171
-rw-r--r--tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.c0
-rw-r--r--tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.cpp0
-rw-r--r--tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.m0
-rw-r--r--tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.mm0
-rw-r--r--tests/auto/blackbox/testdata/configure/configure.qbs (renamed from tests/manual/configure/configure.qbs)0
-rw-r--r--tests/auto/blackbox/testdata/configure/main.cpp (renamed from tests/manual/configure/main.cpp)0
-rw-r--r--tests/auto/blackbox/testdata/configure/modules/definition/module.qbs (renamed from tests/manual/configure/modules/definition/module.qbs)0
-rw-r--r--tests/auto/blackbox/testdata/cpu-features/cpu-features.qbs16
-rw-r--r--tests/auto/blackbox/testdata/cpu-features/main.cpp1
-rw-r--r--tests/auto/blackbox/testdata/discard-unused-data/discard-unused-data.qbs32
-rw-r--r--tests/auto/blackbox/testdata/discard-unused-data/main.cpp3
-rw-r--r--tests/auto/blackbox/testdata/external-libs/external-libs.qbs40
-rw-r--r--tests/auto/blackbox/testdata/includeLookup/includeLookup.qbs (renamed from tests/manual/includeLookup/includeLookup.qbs)0
-rw-r--r--tests/auto/blackbox/testdata/includeLookup/main.cpp (renamed from tests/manual/includeLookup/main.cpp)0
-rw-r--r--tests/auto/blackbox/testdata/includeLookup/modules/definition/fakeopenssl/sha.h0
-rw-r--r--tests/auto/blackbox/testdata/includeLookup/modules/definition/module.qbs (renamed from tests/manual/includeLookup/modules/definition/module.qbs)4
-rw-r--r--tests/auto/blackbox/testdata/installable/installable.qbs2
-rw-r--r--tests/auto/blackbox/testdata/installpackage/installpackage.qbs2
-rw-r--r--tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs1
-rw-r--r--tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs15
-rw-r--r--tests/auto/blackbox/testdata/list-products/list-products.qbs16
-rw-r--r--tests/auto/blackbox/testdata/localDeployment/localDeployment.qbs (renamed from tests/manual/localDeployment/localDeployment.qbs)9
-rw-r--r--tests/auto/blackbox/testdata/localDeployment/main.cpp54
-rw-r--r--tests/auto/blackbox/testdata/minimumSystemVersion/fakewindows.qbs16
-rw-r--r--tests/auto/blackbox/testdata/minimumSystemVersion/macappstore.qbs12
-rw-r--r--tests/auto/blackbox/testdata/minimumSystemVersion/main.cpp99
-rw-r--r--tests/auto/blackbox/testdata/minimumSystemVersion/main.mm60
-rw-r--r--tests/auto/blackbox/testdata/minimumSystemVersion/specific.qbs26
-rw-r--r--tests/auto/blackbox/testdata/minimumSystemVersion/unspecified-forced.qbs23
-rw-r--r--tests/auto/blackbox/testdata/minimumSystemVersion/unspecified.qbs18
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs2
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/subdir/file1.cpp (renamed from tests/auto/blackbox/testdata/nested-groups/file1.cpp)0
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/subdir/file1.h (renamed from tests/auto/blackbox/testdata/nested-groups/file1.h)0
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/subdir/file2.cpp (renamed from tests/auto/blackbox/testdata/nested-groups/file2.cpp)0
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/subdir/file2.h (renamed from tests/auto/blackbox/testdata/nested-groups/file2.h)0
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/subdir/main2.cpp (renamed from tests/auto/blackbox/testdata/nested-groups/main2.cpp)0
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/subdir/main3.cpp (renamed from tests/auto/blackbox/testdata/nested-groups/main3.cpp)0
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/subdir/other.cpp (renamed from tests/auto/blackbox/testdata/nested-groups/other.cpp)0
-rw-r--r--tests/auto/blackbox/testdata/nested-groups/subdir/other.h (renamed from tests/auto/blackbox/testdata/nested-groups/other.h)0
-rw-r--r--tests/auto/blackbox/testdata/no-profile/no-profile.qbs5
-rw-r--r--tests/auto/blackbox/testdata/system-include-paths/main.cpp8
-rw-r--r--tests/auto/blackbox/testdata/system-include-paths/subdir/gagagugu.h4
-rw-r--r--tests/auto/blackbox/testdata/system-include-paths/system-include-paths.qbs6
-rw-r--r--tests/auto/blackbox/testdata/variant-suffix/lib.cpp1
-rw-r--r--tests/auto/blackbox/testdata/variant-suffix/variant-suffix.qbs43
-rw-r--r--tests/auto/blackbox/testdata/vcs/main.cpp7
-rw-r--r--tests/auto/blackbox/testdata/vcs/vcstest.qbs6
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp638
-rw-r--r--tests/auto/blackbox/tst_blackbox.h17
-rw-r--r--tests/auto/blackbox/tst_blackboxandroid.cpp154
-rw-r--r--tests/auto/blackbox/tst_blackboxandroid.h (renamed from tests/manual/localDeployment/main.cpp)32
-rw-r--r--tests/auto/blackbox/tst_blackboxapple.cpp18
-rw-r--r--tests/auto/blackbox/tst_blackboxbase.cpp7
-rw-r--r--tests/auto/blackbox/tst_blackboxjava.cpp84
-rw-r--r--tests/auto/blackbox/tst_blackboxjava.h5
-rw-r--r--tests/auto/blackbox/tst_blackboxqt.cpp70
-rw-r--r--tests/auto/blackbox/tst_blackboxqt.h3
-rw-r--r--tests/auto/cmdlineparser/tst_cmdlineparser.cpp3
-rw-r--r--tests/auto/language/testdata/additional-product-types.qbs14
-rw-r--r--tests/auto/language/testdata/broken-dependency-cycle1.qbs20
-rw-r--r--tests/auto/language/testdata/broken-dependency-cycle2.qbs20
-rw-r--r--tests/auto/language/testdata/conditionaldepends.qbs10
-rw-r--r--tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/bar/modules/conflicting-instances/bar.qbs2
-rw-r--r--tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/foo/modules/conflicting-instances/foo.qbs2
-rw-r--r--tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/product.qbs5
-rw-r--r--tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/project.qbs6
-rw-r--r--tests/auto/language/testdata/erroneous/conflicting-module-instances.qbs5
-rw-r--r--tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance1.qbs2
-rw-r--r--tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance2.qbs2
-rw-r--r--tests/auto/language/testdata/exports.qbs18
-rw-r--r--tests/auto/language/testdata/filetags.qbs20
-rw-r--r--tests/auto/language/testdata/modules/dummy/dummy.qbs3
-rw-r--r--tests/auto/language/testdata/modules/dummy2/dummy2.qbs2
-rw-r--r--tests/auto/language/testdata/modules/dummy3_loader/dummy3_loader.qbs6
-rw-r--r--tests/auto/language/testdata/multiplexing-by-profile/p1.qbs17
-rw-r--r--tests/auto/language/testdata/multiplexing-by-profile/p2.qbs21
-rw-r--r--tests/auto/language/testdata/multiplexing-by-profile/p3.qbs29
-rw-r--r--tests/auto/language/testdata/multiplexing-by-profile/p4.qbs21
-rw-r--r--tests/auto/language/tst_language.cpp118
-rw-r--r--tests/auto/language/tst_language.h5
-rw-r--r--tests/auto/shared.h7
-rw-r--r--tests/auto/tools/tst_tools.cpp189
-rw-r--r--tests/auto/tools/tst_tools.h9
-rw-r--r--tests/manual/minimumSystemVersion/main.cpp103
-rw-r--r--tests/manual/minimumSystemVersion/minimumSystemVersion.qbs76
-rw-r--r--tests/manual/run-qbs-tests.bat8
-rwxr-xr-xtests/manual/run-qbs-tests.sh11
369 files changed, 13784 insertions, 2576 deletions
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 000000000..dc306c9fe
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,28 @@
+<ABBAPOH@gmail.com> <ABBAPOH@me.com>
+<bogdan@kdab.com> <bogdan@kde.org>
+<christian.kandeler@qt.io> <christian.kandeler@digia.com>
+<christian.kandeler@qt.io> <christian.kandeler@nokia.com>
+<christian.kandeler@qt.io> <christian.kandeler@theqtcompany.com>
+<christian.stenger@qt.io> <christian.stenger@digia.com>
+<eike.ziller@qt.io> <eike.ziller@digia.com>
+<eike.ziller@qt.io> <eike.ziller@theqtcompany.com>
+<friedemann.kleint@qt.io> <Friedemann.Kleint@nokia.com>
+<hjk@qt.io> <qtc-committer@nokia.com>
+<hjk@qt.io> <qthjk@ovi.com>
+<jake.petroules@qt.io> <jake.petroules@petroules.com>
+<jake.petroules@qt.io> <jake.petroules@theqtcompany.com>
+<joerg.bornemann@qt.io> <joerg.bornemann@digia.com>
+<joerg.bornemann@qt.io> <joerg.bornemann@nokia.com>
+<joerg.bornemann@qt.io> <joerg.bornemann@theqtcompany.com>
+<kai.koehne@qt.io> <kai.koehne@digia.com>
+<kai.koehne@qt.io> <kai.koehne@theqtcompany.com>
+<marco.bubke@qt.io> <marco.bubke@nokia.com>
+<orgad.shaneh@audiocodes.com> <orgads@gmail.com>
+<oswald.buddenhagen@qt.io> <oswald.buddenhagen@digia.com>
+<oswald.buddenhagen@qt.io> <oswald.buddenhagen@theqtcompany.com>
+<riitta-leena.miettinen@qt.io> <riitta-leena.miettinen@digia.com>
+<riitta-leena.miettinen@qt.io> <riitta-leena.miettinen@nokia.com>
+<riitta-leena.miettinen@qt.io> <riitta-leena.miettinen@theqtcompany.com>
+<robert.loehning@qt.io> <robert.loehning@digia.com>
+<tobias.hunger@qt.io> <tobias.hunger@digia.com>
+<topi.reinio@qt.io> <topi.reinio@digia.com>
diff --git a/README b/README
index 54352a956..5c11c8465 100644
--- a/README
+++ b/README
@@ -2,35 +2,7 @@ Qbs
===
Qbs is a cross-platform build tool.
The project's homepage is http://wiki.qt.io/qbs
+Qbs product documentation is available at: http://doc.qt.io/qbs/index.html
-Supported Platforms
-===================
-
-Windows XP SP2 or later
-macOS 10.7 or later
-Linux (tested on Debian 6/7 and Ubuntu 13)
-
-Building the sources requires Qt 5.6.0 or later.
-
-Build Instructions
-==================
-Prerequisites:
- * Qt 5.6.0 or later
- * On Windows:
- - MinGW or Visual Studio
- * On macOS: Xcode
-
-The installed toolchains have to match the one Qt was compiled with.
-
-You can build Qbs with
-
- cd $SOURCE_DIRECTORY
- qmake -r
- make (or mingw32-make or nmake or jom, depending on your platform)
-
-Installation ("make install") is not needed. It is however possible, using
-
- make install INSTALL_ROOT=$INSTALL_DIRECTORY
-
-You can also build and develop Qbs using Docker. For more information,
-see "Appendix A: Building Qbs" in the Qbs Manual.
+For more information about building Qbs from sources, see
+"Appendix A: Building Qbs" at: http://doc.qt.io/qbs/building-qbs.html.
diff --git a/VERSION b/VERSION
index 9ab8337f3..81c871de4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.9.1
+1.10.0
diff --git a/doc/doc.pri b/doc/doc.pri
index 84583a870..cd05a9450 100644
--- a/doc/doc.pri
+++ b/doc/doc.pri
@@ -14,3 +14,5 @@ fixnavi.commands = \
perl fixnavi.pl -Dqcmanual -Dqtquick \
qbs.qdoc
QMAKE_EXTRA_TARGETS += fixnavi
+
+include(man/man.pri)
diff --git a/doc/doc.qbs b/doc/doc.qbs
index 1dba7dae9..2a0e16587 100644
--- a/doc/doc.qbs
+++ b/doc/doc.qbs
@@ -9,11 +9,17 @@ Product {
Depends { name: "qbsversion" }
files: [
+ "../README",
+ "classic.css",
"external-resources.qdoc",
+ "fixnavi.pl",
"howtos.qdoc",
"qbs.qdoc",
+ "qbs-online.qdocconf",
"config/*.qdocconf",
+ "config/style/qt5-sidebar.html",
"reference/**/*",
+ "templates/**/*",
]
Group {
name: "main qdocconf file"
diff --git a/doc/man/man.pri b/doc/man/man.pri
new file mode 100644
index 000000000..2e29f9112
--- /dev/null
+++ b/doc/man/man.pri
@@ -0,0 +1,5 @@
+qbs_no_man_install: return()
+
+man.files = $$PWD/qbs.1
+man.path = $${QBS_INSTALL_PREFIX}/share/man/man1
+INSTALLS += man
diff --git a/doc/man/man.qbs b/doc/man/man.qbs
new file mode 100644
index 000000000..99db2b3f8
--- /dev/null
+++ b/doc/man/man.qbs
@@ -0,0 +1,66 @@
+import qbs
+import qbs.File
+import qbs.FileInfo
+import qbs.Probes
+import qbs.Utilities
+
+Product {
+ name: "qbs man page"
+ type: ["manpage"]
+
+ Depends { name: "qbs_app"; condition: updateManPage }
+ Depends { name: "qbsbuildconfig" }
+
+ property bool updateManPage: false
+ property string help2ManFilePath: help2ManProbe.filePath
+
+ Group {
+ name: "man page"
+ files: ["qbs.1"]
+ qbs.install: qbsbuildconfig.installManPage
+ qbs.installDir: "share/man/man1"
+ }
+
+ Group {
+ name: "additional sections"
+ files: ["see-also.h2m"]
+ fileTags: ["man.section"]
+ }
+
+ Rule {
+ condition: updateManPage
+ multiplex: true
+ explicitlyDependsOn: ["application"]
+ inputs: ["man.section"]
+ Artifact {
+ filePath: "qbs.1"
+ fileTags: ["manpage"]
+ }
+ prepare: {
+ var help2ManExe = product.help2ManFilePath;
+ if (!help2ManExe)
+ throw "Cannot update man page: help2man not available";
+ if (Utilities.versionCompare(product.qbs.version, "1.9") < 0)
+ throw "Cannot update man page: qbs >= 1.9 required";
+ var args = [explicitlyDependsOn.application[0].filePath, "-o", output.filePath,
+ "--no-info", "--name=the Qbs build tool"];
+ var sections = inputs ? inputs["man.section"] : [];
+ for (var i = 0; i < sections.length; ++i)
+ args.push("--include=" + sections[i].filePath);
+ var help2ManCmd = new Command(help2ManExe, args);
+ help2ManCmd.description = "creating man page";
+ var copyCmd = new JavaScriptCommand();
+ copyCmd.silent = true;
+ copyCmd.sourceCode = function() {
+ File.copy(output.filePath,
+ FileInfo.joinPaths(product.sourceDirectory, output.fileName));
+ }
+ return [help2ManCmd, copyCmd];
+ }
+ }
+
+ Probes.BinaryProbe {
+ id: help2ManProbe
+ names: ["help2man"]
+ }
+}
diff --git a/doc/man/qbs.1 b/doc/man/qbs.1
new file mode 100644
index 000000000..a26ef9b50
--- /dev/null
+++ b/doc/man/qbs.1
@@ -0,0 +1,79 @@
+.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
+.TH QBS "1" "August 2017" "qbs 1.10.0" "User Commands"
+.SH NAME
+qbs \- the Qbs build tool
+.SH SYNOPSIS
+.B qbs
+[\fI\,command\/\fR] [\fI\,command parameters\/\fR]
+.SH DESCRIPTION
+Qbs 1.10.0, a cross\-platform build tool.
+.SS "Built-in commands:"
+.TP
+build
+Build (parts of) a project. This is the default command.
+.TP
+clean
+Remove the files generated during a build.
+.TP
+dump\-nodes\-tree
+Dumps the nodes in the build graph to stdout.
+.TP
+generate
+Generate project files for another build tool.
+.TP
+help
+Show general or command\-specific help.
+.TP
+install
+Install (parts of) a project.
+.TP
+list\-products
+Lists all products in the project, including sub\-projects.
+.TP
+resolve
+Resolve a project without building it.
+.TP
+run
+Run an executable generated by building a project.
+.TP
+shell
+Open a shell with a product's environment.
+.TP
+status
+Show the status of files in the project directory.
+.TP
+update\-timestamps
+Mark the build as up to date.
+.SS "Auxiliary commands:"
+.TP
+config
+This tool manages qbs settings.
+.TP
+config\-ui
+This tool displays qbs settings in a GUI.
+.TP
+create\-project
+This tool creates a qbs project from an existing source tree.
+.TP
+qmltypes
+This tool dumps information about the QML types supported by qbs.
+.TP
+setup\-android
+This tool creates qbs profiles from Android SDK and NDK installations.
+.TP
+setup\-qt
+This tool creates qbs profiles from Qt versions.
+.TP
+setup\-toolchains
+This tool creates qbs profiles from toolchains.
+.SH "SEE ALSO"
+For detailed help on a command, use the
+.B help
+command. For instance:
+.IP
+qbs help build
+.PP
+
+The full documentation for
+.B qbs
+is available at <http://qbs.io>.
diff --git a/doc/man/see-also.h2m b/doc/man/see-also.h2m
new file mode 100644
index 000000000..7de95809b
--- /dev/null
+++ b/doc/man/see-also.h2m
@@ -0,0 +1,11 @@
+[SEE ALSO]
+For detailed help on a command, use the
+.B help
+command. For instance:
+.IP
+qbs help build
+.PP
+
+The full documentation for
+.B qbs
+is available at <http://qbs.io>.
diff --git a/doc/qbs.qdoc b/doc/qbs.qdoc
index 256d840e6..0be7781d5 100644
--- a/doc/qbs.qdoc
+++ b/doc/qbs.qdoc
@@ -45,9 +45,6 @@
developing projects across multiple platforms. \QBS can be used for any
software project, regardless of programming language, toolkit, or libraries used.
- \QBS is an all-in-one tool that generates a build graph from a high-level
- project description (like qmake or CMake) and additionally undertakes the
- task of executing the commands in the low-level build graph (like make).
\note Please report bugs and suggestions to the
\l{http://bugreports.qt.io/}{Qt Bug Tracker}.
@@ -98,10 +95,224 @@
\title Introduction
- \QBS builds applications based on the information in a project file that you
- specify in a QML dialect. Each project file specifies one project that can
- contain several \l{Product Item}{products}. You specify the type of the product: application,
- library, and so on.
+ \QBS is a build automation tool designed to conveniently manage the build
+ process of real-life software projects across multiple platforms. It
+ provides the following benefits:
+
+ \list
+ \li Declarative paradigm
+ \li Well-defined language
+ \li Platform and programming language independence
+ \li Correct and fast incremental builds
+ \li Extensible architecture
+ \li Easy integration to IDEs
+ \endlist
+
+ \section1 Declarative Paradigm
+
+ When writing a project, it is important to describe the build tasks and
+ dependencies between them, rather than the build order. It is difficult to
+ determine the correct build order in complex projects, especially during
+ parallel builds. The build tool should bear that burden, not the developer.
+
+ With a declarative language, \QBS enables you to express intent rather than
+ specifying single build steps. This provides the appropriate level of
+ abstraction for a build system. For example, \e dependencies can be created
+ between \e products, such that the target \e artifacts of the dependency
+ can be used as input to the build \e rules in the context of the depending
+ product. In addition, you can \e export dependencies and \e properties to
+ other products.
+
+ \QBS is modular with clean interfaces between modules. A \e module is a
+ collection of properties and \e {language items} that are used for
+ building a product if the product depends on the module. The properties
+ that can be set for a module are used to control the behavior of the
+ toolchain used to build the module.
+
+ \QBS itself knows nothing about file types or extensions, and therefore all
+ source files in a product are handled equally. However, you can assign
+ \e {file tags} to an artifact to act as markers or to specify a file type.
+ \QBS applies a rule to the source files of the project and chooses the
+ ones that match the input file tags specified by the rule. It then creates
+ artifacts in the build graph that have other filenames and file tags.
+
+ Products and projects can contain \e probes that are run prior to building,
+ for instance to locate dependent headers, libraries, and other files outside
+ the project directory.
+
+ \section1 Well-Defined Language
+
+ \QBS projects are specified in a QML dialect. QML is a concise, easy to
+ learn, and intuitive language that is used successfully in the Qt project.
+ Its core is declarative, but it can be extended with JavaScript snippets
+ for extra flexibility.
+
+ \QBS builds applications based on the information in a project file. Each
+ project file specifies one \l{Project Item}{project} that can contain
+ several \l{Product Item}{products}. You specify the type of the product,
+ such as an \e application, and the dependencies the product has on other
+ products.
+
+ The product type determines the set of \l{Rule Item}{rules} that \QBS
+ applies to produce artifacts from input files. The input files can be
+ divided into \l{Group Item}{groups} according to their type or purpose, for
+ example. A group can also be used to attach \l{Properties Item}{properties}
+ to products.
+
+ The following is an example of a minimal project file that specifies the
+ product type, application name, source file, and a dependency on the
+ \l{Module cpp}{cpp module}:
+
+ \code
+ import qbs
+
+ Application {
+ name: "helloworld"
+ files: "main.cpp"
+ Depends { name: "cpp" }
+ }
+ \endcode
+
+ For more information, see \l{Language Introduction}.
+
+ \section1 Platform and Programming Language Independence
+
+ \QBS can be used for any software project, regardless of programming
+ language, toolkit, or libraries used. \QBS has built-in support for
+ building applications for Windows, Linux, macOS, Android, iOS, tvOS,
+ watchOS, QNX, and FreeBSD, as well as for cross-compilation. It can be
+ easily extended to support further platforms.
+
+ Invoking \c {qbs build} from the command line automatically builds the
+ project for the current host platform using the best available toolchain and
+ settings, unless a default profile is set. You can configure additional
+ profiles for each toolchain you want to use and select the profile to use
+ at build time.
+
+ For example, to build applications for Android devices, you would need to
+ set up a profile for the Android toolchain and select it when you build the
+ application. If you name the profile \e Android, you would then enter the
+ following command:
+
+ \code
+ qbs build profile:Android
+ \endcode
+
+ For more information, see \l{Building Applications}.
+
+ Platform and programming language support is implemented as a set of
+ \l{List of Modules}{modules} that your product depends on. In the language
+ example above, the dependency on the \l{Module cpp}{cpp module} determines
+ that the C++ sources are compiled and linked into a binary.
+
+ Alternatively, you could use the \l{CppApplication Item}{CppApplication}
+ convenience item that implies a dependency on the \c cpp module:
+
+ \code
+ CppApplication {
+ name: "helloworld"
+ files: "main.cpp"
+ }
+ \endcode
+
+ Additionally, if the sources use Qt, you need a dependency to the
+ \l{Qt Modules}{Qt.core module}, and so on.
+
+ In addition to building projects, \QBS can install the build artifacts to
+ a location from where they can be run on the desktop or on a device. \QBS
+ modules can be used to create installers for the end users of the
+ applications. For example, the \l{Module dmg}{dmg} module contains
+ properties and rules for building Apple Disk Images, which are typically
+ used to distribute applications and installers on macOS. The
+ \l{Module innosetup}{innosetup}, \l{Module nsis}{nsis}, and \l{Module wix}
+ {wix} modules contain properties and rules for building installers for
+ Windows platforms.
+
+ \section1 Correct and Fast Incremental Builds
+
+ \QBS is an all-in-one tool that generates a build graph from a high-level
+ project description (like qmake or CMake) and additionally undertakes the
+ task of executing the commands in the low-level build graph (like make).
+
+ \QBS automatically takes advantage of multi-processor and multi-core systems
+ to achieve maximum build parallelization. By default, running \c qbs without
+ any arguments is roughly equivalent to running \c {make -j<n>} where \c n is
+ the number of CPU cores. Similarly, \QBS allows the number of concurrent
+ jobs to be explicitly specified using its own \c -j option.
+
+ \QBS has knowledge about the whole project, and therefore builds remain
+ correct even when you build sub-projects, because \QBS ensures that all
+ dependencies are built too. This virtually eliminates the need for clean
+ builds.
+
+ \QBS uses dynamic build graphs with build rules that can generate a variable
+ number of files and that are executed only when needed. When figuring out
+ which rules to execute, \QBS starts at the product type and then looks for
+ a way to produce artifacts with matching file tags from source files, using
+ a chain of rules that are connected by their respective input and output
+ tags. For an example of how rules are applied when building products, see
+ \l{Rules and Product Types}.
+
+ The \QBS build rules can produce a variable number of outputs.
+ If the input changes, only the required rules are applied at build time.
+ If a rule is applied, all the dependent rules are applied as well, but only
+ those. This feature ensures the correctness of the build graph after source
+ code changes without having to re-configure the whole project.
+
+ Changing properties that do not affect the build, because they are not used
+ by rules, will not cause the project to be rebuilt. The use of properties is
+ tracked. Generated artifacts that cease to exist are deleted to avoid
+ picking outdated generated artifacts and indefinitely increasing the size of
+ the build directory.
+
+ Fast incremental builds are crucial for a fast edit-build-run cycle.
+ Instead of retrieving the timestamps of generated files, \QBS uses the time
+ stamps stored in the build graph. This is especially important on Windows,
+ where file system operations are slow.
+
+ If the project files were not changed, the build graph is loaded from disk.
+ It is stored in a binary format that can be loaded much faster than the real
+ project files. The project files are parsed only if they were changed.
+
+ \section1 Extensible Architecture
+
+ You can create your own custom \l{List of Modules}{modules} and
+ \l{List of Language Items}{items} and make \QBS aware of them.
+
+ You store the custom modules and items in a subdirectory of the project
+ directory and specify the path to the subdirectory as a value of the
+ \c qbsSearchPaths property. For example, if the custom module is located at
+ \c my-modules/modules/modulename/modulename.qbs, you would specify it in the
+ project file as follows:
+
+ \code
+ Project {
+ qbsSearchPaths: "my-modules"
+ \endcode
+
+ For more information, see \l{Custom Modules and Items}.
+
+ \section1 IDE Integration
+
+ \QBS can be used not only from the command line, but also in combination
+ with an IDE, such as Qt Creator, Microsoft Visual Studio, or Xcode.
+ Qt Creator directly supports \QBS projects. Visual Studio and Xcode users
+ can use \QBS to generate Microsoft Visual Studio and Xcode projects.
+ For more information, see \l {Generators}.
+
+ \section2 Qt Creator
+
+ \l{http://doc.qt.io/qtcreator/index.html}{Qt Creator} uses the same \QBS
+ library as the \QBS command line tools. Therefore, it can retrieve all the
+ information required to build a single file or project through a defined
+ public API. Qt Creator provides accurate information about the build
+ progress and displays a project tree that reflects the logical structure of
+ the project, instead of presenting low-level information, such as the file
+ system structure. Adding or removing source files keeps the existing project
+ file structure intact.
+
+ For more information about using \QBS to build projects from Qt Creator, see
+ \l{http://doc.qt.io/qtcreator/creator-project-qbs.html}{Setting Up Qbs}.
*/
@@ -128,9 +339,34 @@
\title Appendix A: Building Qbs
+ \QBS can be \l{Installing}{installed from binary packages} or built from
+ sources, as described in this appendix. In addition, this appendix describes
+ how to use Docker images for developing \QBS.
+
+ \section1 Supported Platforms
+
+ \QBS can be installed and run on the following platforms:
+
+ \list
+ \li Windows 7, or later
+ \li Linux (tested on Debian 8 and 9, Ubuntu 16.04, OpenSuSE 13.2, and
+ Arch Linux)
+ \li macOS 10.7, or later
+ \endlist
+
\section1 System Requirements
- To build \QBS from the source, you need Qt 5.6.0, or later
+ To build \QBS from the source, you need:
+
+ \list
+ \li Qt 5.6.2, or later
+ \li Windows: MinGW with GCC 4.9 or Microsoft Visual Studio 2015,
+ or later
+ \li Linux: GCC 4.9, or later, or Clang 3.9.0, or later
+ \li macOS: Xcode 6.2, or later
+ \endlist
+
+ An installed toolchain has to match the one that Qt was compiled with.
\section1 Building
@@ -140,6 +376,16 @@
qmake -r qbs.pro && make
\endcode
+ Depending on your platform, you might use \c mingw32-make, \c nmake, or
+ \c jom instead of \c make.
+
+ Installation by using \c {make install} is usually not needed. It is however
+ possible, by entering the following command.
+
+ \code
+ make install INSTALL_ROOT=$INSTALL_DIRECTORY
+ \endcode
+
\section1 Configure Options
\QBS recognizes the following qmake CONFIG options to customize the build:
@@ -152,11 +398,12 @@
such as Fedora.
\row \li qbs_no_dev_install \li Exclude header files from installation, that is, perform a
non-developer build.
+ \row \li qbs_no_man_install \li Exclude the man page from installation.
\row \li qbs_enable_project_file_updates \li Enable API for updating project files. This
implies a dependency to the Qt GUI module.
\endtable
- \section1 Docker
+ \section1 Using Docker
A set of Docker images for developing \QBS (which are maintained by the \QBS team) is available
\l{https://hub.docker.com/u/qbsbuild/}{on Docker Hub}.
@@ -659,7 +906,7 @@
\li platform-dependent
\li The target operating system.
May contain "windows", "linux", "macos", "darwin", "unix",
- "ios", "android", "blackberry", "qnx", etc.
+ "ios", "android", "qnx", etc.
\endtable
You can set these properties on the command line or by using a profile.
@@ -863,11 +1110,29 @@
qbs
\endcode
+ By default, \QBS uses all the CPU cores available to achieve maximum build
+ parallelization. To explicitly specify the number of concurrent jobs, use
+ the \c -j option. For example, to run 4 concurrent jobs, enter the following
+ command:
+
+ \code
+ qbs -j4
+ \endcode
+
The application is built using the default build profile that is set up
in your \QBS configuration.
- To build with other profiles, specify options for the build
- command. For example, to build debug and release configurations with a
- profile named "Android", enter the following command:
+
+ You can use the \c config command to set the max number of jobs per profile.
+ For example, to set four jobs as the default option for a profile named
+ \e Android, enter the following command:
+
+ \code
+ qbs config profiles.Android.preferences.jobs 4
+ \endcode
+
+ To build with other profiles than the default one, specify options for the
+ build command. For example, to build debug and release configurations with
+ the \e Android profile, enter the following command:
\code
qbs build profile:Android debug release
@@ -884,7 +1149,7 @@
\c small for release only.
\code
- qbs build debug cpp.treatWarningsAsErrors:true release cpp.optimization:small
+ qbs build debug modules.cpp.treatWarningsAsErrors:true release modules.cpp.optimization:small
\endcode
*/
@@ -936,16 +1201,16 @@
\title Running Applications
- Running ./targets/debug/CollidingMice fails if Qt 4.8 is not in your PATH
- (in Windows) or LD_LIBRARY_PATH (in Linux).
+ By default, running an application also builds it and installs it to a
+ location from where it can be run on the desktop or on a device.
- Therefore, enter the following command to run an application:
+ For example, entering the following command runs the Qt Creator application:
\code
- qbs run --products CollidingMice
+ qbs run --products qtcreator
\endcode
- This command also builds and installs the given product, if necessary.
+ This command also builds and installs the product, if necessary.
*/
@@ -1046,13 +1311,14 @@
\title Generators
- Generators are a \QBS sub-tool and set of APIs which enable arbitrary processing to be performed
- on the build graph. Currently they are used to implement IDE integration with popular IDEs like
- Visual Studio.
+ Generators are a \QBS sub-tool and set of APIs that enable arbitrary
+ processing to be performed on the build graph. Currently, they are used to
+ integrate \QBS with popular IDEs, such as Microsoft Visual Studio, and to
+ generate Clang compilation databases.
- \section1 Using Generators
+ \section1 Generating Projects
- To generate a project for another build system like Visual Studio, use the
+ To generate a project for another build system, such as Microsoft Visual Studio, use the
\c qbs-generate sub-command and specify a generator using the \c{-g} option. For example:
\code
@@ -1061,21 +1327,32 @@
\endcode
\QBS will then generate a series of files in the current directory, based on the generator that
- was chosen. For the Visual Studio generator, the resulting project files can be
- opened in the respective IDEs and all work can be performed there.
+ was chosen. The resulting project files can be opened in the respective IDE
+ and all work can be performed there.
The project files will expose as much information as possible to the IDE and will use \QBS to
- perform the actual build. \note You cannot modify build system files and expect the changes
+ perform the actual build.
+
+ \note You cannot modify build system files and expect the changes
to be reflected in \QBS; you must edit your \QBS project files and re-run \c{qbs-generate} in
order for the changes to be reflected in your IDE.
+ \section1 Generating Clang Compilation Databases
+
+ To generate a \l{https://clang.llvm.org/docs/JSONCompilationDatabase.html}
+ {Clang compilation database (clangdb)}, use the following command:
+
+ \code
+ qbs generate --generator clangdb
+ \endcode
+
\section1 Limitations
- Due to the high flexibility of the Qbs project format and build engine, some projects may be too
+ Due to the high flexibility of the \QBS project format and build engine, some projects may be too
complex to produce an equivalent project file for another build system.
This list of limitations aims to be as small as possible, but one of the most notable (at least
- for the Visual Studio generator) is that certain properties must contain the same
+ for the Microsoft Visual Studio generator) is that certain properties must contain the same
value across all build configurations. For example, the following is not allowed:
\code
@@ -1087,7 +1364,7 @@
}
\endcode
- Note that this limitation only applies when property values are varied on the configuration
+ \note This limitation only applies when property values are varied on the configuration
name. For example, the following is OK (as long as the value of xyz itself does not vary across
configurations):
@@ -1106,8 +1383,9 @@
\li \l{Module bundle}{bundle}.isBundle
\endlist
- If a simple workaround is possible in a particular case (e.g. varying Product.targetName across
- configuration instead of Product.name), the generator will typically suggest it in the error
+ If a simple workaround is possible in a particular case (for example,
+ varying \c Product.targetName across configuration instead of
+ \c Product.name), the generator will typically suggest it in the error
message.
*/
diff --git a/doc/reference/items/language/depends.qdoc b/doc/reference/items/language/depends.qdoc
index 77bc840dd..5107f3f5b 100644
--- a/doc/reference/items/language/depends.qdoc
+++ b/doc/reference/items/language/depends.qdoc
@@ -140,10 +140,10 @@
\row
\li profiles
\li stringList
- \li \c{[product.profile]}
+ \li \c{undefined}
\li If the dependency is on a product and that product is going to be built for more than
one profile, then you can specify here which instance of the product the dependency is on.
- See the \c profiles property of the \c Product item for more information.
+ See the \c profiles property of the \l{Module qbs}{qbs module} for more information.
An empty list means a dependency on all instances of the product with the given name,
regardless of their profile.
\row
diff --git a/doc/reference/items/language/filetagger.qdoc b/doc/reference/items/language/filetagger.qdoc
index 4a87824f2..55fbb4ac9 100644
--- a/doc/reference/items/language/filetagger.qdoc
+++ b/doc/reference/items/language/filetagger.qdoc
@@ -34,9 +34,21 @@
\keyword QML.FileTagger
\title FileTagger Item
- \brief Maps file patterns to tags.
+ \brief Assigns file tags to files.
- This item maps file patterns to tags. It can be attached to a product or a module.
+ This item assigns file tags to files.
+ The FileTagger item can appear in \c{Product} items or \c{Module} items.
+
+ For every source artifact that has no file tag, \QBS will search for a
+ FileTagger with a pattern that matches the file name of the source
+ artifact. If a matching file tagger is found, then the file tags specified
+ in the FileTagger item are assigned to the source artifact.
+
+ If there is more than one matching FileTagger, all file taggers with the
+ same highest priority are taken into account and their file tags are
+ accumulated.
+
+ The FileTagger item can be attached to a product or a module.
In the latter case, its effect is the same as if it had been attached to all products having
a dependency on the respective module. For instance, the cpp module of \QBS has, among others,
the following file tagger:
@@ -71,17 +83,27 @@
\li Property
\li Type
\li Default
+ \li Since
\li Description
\row
\li patterns
\li stringList
\li none
+ \li 1.0
\li The patterns to match against. Supports the usual wildcards '*', '?' and '[]'.
Neither the list itself nor any of its elements may be empty.
\row
\li fileTags
\li list
\li empty list
+ \li 1.0
\li Tags to attach to a product's files. These can then be matched by a rule.
+ \row
+ \li priority
+ \li int
+ \li 0
+ \li 1.10
+ \li The priority of the FileTagger. A higher numerical value means
+ higher priority.
\endtable
*/
diff --git a/doc/reference/items/language/group.qdoc b/doc/reference/items/language/group.qdoc
index 72b9c7f95..e2d66cb0a 100644
--- a/doc/reference/items/language/group.qdoc
+++ b/doc/reference/items/language/group.qdoc
@@ -40,65 +40,65 @@
This item is attached to a product and used to group files that have something in common.
For example:
- \code
- Application {
- Group {
- name: "common files"
- files: ["myclass.h", "myclass_common_impl.cpp"]
- }
- Group {
- name: "Windows files"
- condition: qbs.targetOS.contains("windows")
- files: "myclass_win_impl.cpp"
- }
- Group {
- name: "Unix files"
- condition: qbs.targetOS.contains("unix")
- files: "unixhelper.cpp"
- Group {
- name: "Linux files"
- condition: qbs.targetOS.contains("linux")
- files: "myclass_linux_impl.cpp"
- }
- Group {
- name: "FreeBSD files"
- condition: qbs.targetOS.contains("freebsd")
- files: "myclass_freebsd_impl.cpp"
- }
- }
- Group {
- name: "Files to install"
- qbs.install: true
- qbs.installDir: "share"
- files: "runtime_resource.txt"
- }
- }
- \endcode
- When specifying files, you can use the wildcards "*", "?" and "[]", which have their usual meaning.
- By default, matching files are only picked up directly from the parent directory, but you can tell \QBS to
- consider the whole directory tree. It is also possible to exclude certain files from the list.
- The pattern ** used in a pathname expansion context will match all files and zero or more
- directories and subdirectories.
- For example:
- \snippet reference/items/language/group.qbs 0
+ \code
+ Application {
+ Group {
+ name: "common files"
+ files: ["myclass.h", "myclass_common_impl.cpp"]
+ }
+ Group {
+ name: "Windows files"
+ condition: qbs.targetOS.contains("windows")
+ files: "myclass_win_impl.cpp"
+ }
+ Group {
+ name: "Unix files"
+ condition: qbs.targetOS.contains("unix")
+ files: "unixhelper.cpp"
+ Group {
+ name: "Linux files"
+ condition: qbs.targetOS.contains("linux")
+ files: "myclass_linux_impl.cpp"
+ }
+ Group {
+ name: "FreeBSD files"
+ condition: qbs.targetOS.contains("freebsd")
+ files: "myclass_freebsd_impl.cpp"
+ }
+ }
+ Group {
+ name: "Files to install"
+ qbs.install: true
+ qbs.installDir: "share"
+ files: "runtime_resource.txt"
+ }
+ }
+ \endcode
+ When specifying files, you can use the wildcards "*", "?" and "[]", which have their usual meaning.
+ By default, matching files are only picked up directly from the parent directory, but you can tell \QBS to
+ consider the whole directory tree. It is also possible to exclude certain files from the list.
+ The pattern ** used in a pathname expansion context will match all files and zero or more
+ directories and subdirectories.
+ For example:
+ \snippet reference/items/language/group.qbs 0
- A group can also be used to attach properties to build artifacts such as executables or
- libraries. In the following example, an application is installed to "<install root>/bin".
- \code
- Application {
- Group {
- fileTagsFilter: "application"
- qbs.install: true
- qbs.installDir: "bin"
- }
- }
- \endcode
+ A group can also be used to attach properties to build artifacts such as executables or
+ libraries. In the following example, an application is installed to "<install root>/bin".
+ \code
+ Application {
+ Group {
+ fileTagsFilter: "application"
+ qbs.install: true
+ qbs.installDir: "bin"
+ }
+ }
+ \endcode
- Groups may also appear in modules, which causes the respective sources to be added to the
- products depending on said module.
- Groups can be nested. In this case, child groups inherit the module properties and the file
- tags of their parent group. The condition of a child group gets logically ANDed with the one
- of its parent group.
+ Groups may also appear in modules, which causes the respective sources to be added to the
+ products depending on said module.
+ Groups can be nested. In this case, child groups inherit the module properties and file
+ tags as well as the prefix of their parent group. The condition of a child group gets logically
+ ANDed with the one of its parent group.
\section1 Group Properties
@@ -124,7 +124,7 @@
\row
\li prefix
\li string
- \li empty string
+ \li prefix of the parent group if one exists, otherwise empty
\li A string to prepend to all files. Slashes are allowed and have directory semantics.
\row
\li fileTagsFilter
@@ -132,7 +132,12 @@
\li empty list
\li Artifact file tags to match. Any properties set in this group will be applied
to the product's artifacts whose file tags intersect with the ones
- listed here. Mutually exclusive with files.
+ listed here.
+
+ The file tags that the group's \c{fileTags} property specifies will
+ be added to the matching artifacts.
+
+ This property is mutually exclusive with \c{files}.
\row
\li condition
\li bool
@@ -153,6 +158,8 @@
If this property is true, then the file tags set via the group
replace the ones set via the product or parent group.
If it is false, the "group tags" are added to the "parent tags".
+
+ This property is ignored if \c{fileTagsFilter} is set.
\row
\li excludeFiles
\li list
diff --git a/doc/reference/items/language/product.qdoc b/doc/reference/items/language/product.qdoc
index b96752f9f..9574190dc 100644
--- a/doc/reference/items/language/product.qdoc
+++ b/doc/reference/items/language/product.qdoc
@@ -28,7 +28,7 @@
\contentspage list-of-language-items.html
\previouspage probe-item.html
\page product-item.html
- \nextpage project-item.html
+ \nextpage profile-item.html
\ingroup list-of-language-items
\ingroup list-of-items
\keyword QML.Product
diff --git a/doc/reference/items/language/profile.qdoc b/doc/reference/items/language/profile.qdoc
new file mode 100644
index 000000000..e654fa34e
--- /dev/null
+++ b/doc/reference/items/language/profile.qdoc
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+/*!
+ \contentspage list-of-language-items.html
+ \previouspage product-item.html
+ \page profile-item.html
+ \nextpage project-item.html
+ \ingroup list-of-language-items
+ \ingroup list-of-items
+ \keyword QML.Properties
+
+ \title Profile Item
+ \brief Creates a profile within the project
+
+ The profiles used by \QBS are normally set up on a user's machine and are then available
+ to all projects. See the \l Configuring section for information on how to set up and
+ use profiles on the command line.
+ In some rare cases, however, the creator of a project has complete knowledge about the system
+ on which that project is to be built. Then it can make sense to integrate the profile into
+ the project:
+
+ \code
+ Product {
+ // ...
+ Profile {
+ name: "my-special-profile"
+ qbs.toolchain: ["gcc"]
+ qbs.targetOS: ["linux", "unix"]
+ qbs.architecture: "armv7a"
+ cpp.toolchainInstallPath: "/opt/special-gcc/bin"
+ cpp.toolchainPrefix: "arm-linux-gnueabi-"
+ }
+ qbs.profiles: ["my-special-profile"]
+ // ...
+ }
+ \endcode
+
+ The project in the above example can be built in a particular well-known environment
+ without any additional setup.
+
+ \c Profile items can appear inside \l{Product Item}{Product} and \l{Project Item}{Project}
+ items.
+
+ \section1 Profile Properties
+
+ \table
+ \header
+ \li Property
+ \li Type
+ \li Default
+ \li Description
+ \row
+ \li baseProfile
+ \li \c string
+ \li \c undefined
+ \li The name of a profile from which this profile inherits. If the same property is
+ set in both this profile and the base profile, the value from this profile
+ takes precedence.
+ \row
+ \li condition
+ \li \c bool
+ \li \c true
+ \li If this property is set to \c false, the profile cannot be used.
+ \row
+ \li name
+ \li \c string
+ \li \c undefined
+ \li The name under which the profile can be referenced later. Setting this property
+ is required. The value must be unique among all profiles in an entire project.
+ \endtable
+*/
diff --git a/doc/reference/items/language/project.qdoc b/doc/reference/items/language/project.qdoc
index 581d20e21..18330f574 100644
--- a/doc/reference/items/language/project.qdoc
+++ b/doc/reference/items/language/project.qdoc
@@ -26,7 +26,7 @@
****************************************************************************/
/*!
\contentspage list-of-language-items.html
- \previouspage product-item.html
+ \previouspage profile-item.html
\page project-item.html
\nextpage properties-item.html
\ingroup list-of-language-items
diff --git a/doc/reference/jsextensions/jsextension-fileinfo.qdoc b/doc/reference/jsextensions/jsextension-fileinfo.qdoc
index 1dbd7564f..d656ac8e8 100644
--- a/doc/reference/jsextensions/jsextension-fileinfo.qdoc
+++ b/doc/reference/jsextensions/jsextension-fileinfo.qdoc
@@ -44,6 +44,13 @@
\endcode
Returns the file name of \c filePath up to (but not including) the first '.' character.
+ \section2 cleanPath
+ \code
+ FileInfo.cleanPath(filePath: string): string
+ \endcode
+ Returns \c filePath without redundant separators and with resolved occurrences of
+ \c{.} and \c{..} components. For instance, \c{/usr/local//../bin/} becomes \c{/usr/bin}.
+
\section2 completeBaseName
\code
FileInfo.completeBaseName(filePath: string): string
diff --git a/doc/reference/jsextensions/jsextension-textfile.qdoc b/doc/reference/jsextensions/jsextension-textfile.qdoc
index 2dec89e28..65758b3bd 100644
--- a/doc/reference/jsextensions/jsextension-textfile.qdoc
+++ b/doc/reference/jsextensions/jsextension-textfile.qdoc
@@ -39,10 +39,12 @@
\section2 TextFile.OpenMode
\code
- enum TextFile.OpenMode { ReadOnly, WriteOnly, ReadWrite }
+ enum TextFile.OpenMode { ReadOnly, WriteOnly, ReadWrite, Append }
\endcode
List of modes that a file may be opened in.
+ The OpenMode values can be combined with the bitwise or operator.
+
\section1 Available operations
\section2 Constructor
@@ -65,6 +67,12 @@
Closes the file. It is recommended to always call this function as soon as you are finished
with the file, in order to keep the number of in-flight file descriptors as low as possible.
+ \section2 filePath
+ \code
+ filePath(): string
+ \endcode
+ The absolute path of the file represented by this object.
+
\section2 readAll
\code
readAll(): string
diff --git a/doc/reference/modules/android-ndk-module.qdoc b/doc/reference/modules/android-ndk-module.qdoc
index 006d7e5a6..c620f9dc7 100644
--- a/doc/reference/modules/android-ndk-module.qdoc
+++ b/doc/reference/modules/android-ndk-module.qdoc
@@ -62,9 +62,6 @@
\section1 Android.ndk Properties
- These properties are set automatically when creating an Android profile via the
- \c setup-android tool.
-
\table
\header
\li Property
diff --git a/doc/reference/modules/android-sdk-module.qdoc b/doc/reference/modules/android-sdk-module.qdoc
index 197a80717..fb04c140b 100644
--- a/doc/reference/modules/android-sdk-module.qdoc
+++ b/doc/reference/modules/android-sdk-module.qdoc
@@ -42,9 +42,6 @@
\section1 Android.sdk Properties
- These properties are set automatically when creating an Android profile via the
- \c setup-android tool.
-
\table
\header
\li Property
@@ -57,8 +54,8 @@
\li string
\li 1.4
\li undefined
- \li The version of the build tools such as aapt and dx. The \c setup-android
- tool sets this to the highest version available in the SDK.
+ \li The version of the build tools such as aapt and dx. By default,
+ this is set to the highest version available in the SDK.
\row
\li ndkDir
\li string
@@ -70,8 +67,8 @@
\li string
\li 1.4
\li undefined
- \li The versioned platform name (e.g. "android-21"). The \c setup-android
- tool sets this to the highest version available in the SDK.
+ \li The versioned platform name (e.g. "android-21"). By default,
+ this is set to the highest version available in the SDK.
\row
\li sdkDir
\li string
@@ -80,6 +77,25 @@
\li The SDK base directory.
\endtable
+ \section1 Dependency Parameters
+
+ \table
+ \header
+ \li Parameter
+ \li Type
+ \li Since
+ \li Default
+ \li Description
+ \row
+ \li \c{embedJar}
+ \li \c{bool}
+ \li 1.10
+ \li \c{true}
+ \li If \c{true}, then if the dependency is a JAR file, its classes and the classes of its
+ dependencies (if \c{embedJar} is also true for them) will be recursively processed by
+ \c{dex} and included in the final APK.
+ \endtable
+
\section1 Relevant File Tags
\table
diff --git a/doc/reference/modules/cpp-module.qdoc b/doc/reference/modules/cpp-module.qdoc
index 4415fe731..e7af164bd 100644
--- a/doc/reference/modules/cpp-module.qdoc
+++ b/doc/reference/modules/cpp-module.qdoc
@@ -50,7 +50,7 @@
\li allowUnresolvedSymbols
\li \c{bool}
\li 1.2
- \li \c{false}
+ \li \c{undefined}
\li Switch this on if you want the linking step to succeed even if the resulting binary
contains unresolved symbols. Normally this makes little sense, but in special cases it
is possible that the respective symbols will be available at load time even if they are
@@ -115,6 +115,14 @@
\li Like \c combineCSources, but for Objective-C++. The relevant file tags are \c{"objcpp"}
and \c{"objcpp.combine"}.
\row
+ \li discardUnusedData
+ \li \c{bool}
+ \li 1.10
+ \li \c undefined
+ \li If this property is \c true, the linker will discard data from objects
+ that it determines to be unused. With MSVC and on Apple platforms, the granularity is
+ per symbol, elsewhere it is per section.
+ \row
\li separateDebugInformation
\li \c{bool}
\li 1.4
@@ -682,6 +690,22 @@
\li \c{true} on non-Darwin Unix platforms or when targeting macOS 10.4.x and older.
\li Use the \c{-rpath-link} linker option for transitive shared objects.
\row
+ \li variantSuffix
+ \li \c{string}
+ \li 1.10
+ \li platform-specific; see below
+ \li A suffix to add to a product's target name if that product is of type
+ \c staticlibrary or \c dynamiclibrary. Additionally, on Darwin platforms,
+ applications and loadable modules are also affected. By default, it is empty on
+ all platforms unless the product is multiplexed over the \c {qbs.buildVariants}
+ property. In that case, for the debug variant of the product, the default value
+ is \c{"d"} on Windows and \c{"_debug"} on Darwin platforms such as macOS. On all other
+ platforms and in release mode, the default value is empty.
+
+ For example, building a dynamic library called \c MyLib that is multiplexed over the
+ \c{qbs.buildVariants} property with MSVC will produce files called \c{MyLib.dll}
+ (for the release version of the product) and \c{MyLibd.dll} (for the debug version).
+ \row
\li visibility
\li \c{string}
\li 1.0
@@ -716,6 +740,40 @@
preprocessor symbols UNICODE and _UNICODE, "mbcs" will define _MBCS, and
setting the value to undefined will use the default character set.
\row
+ \li windowsApiFamily
+ \li \c{string}
+ \li 1.10
+ \li \c{undefined}
+ \li Specifies the Windows API family that the application or library is targeting.
+ This is used when building Universal Windows Platform applications.
+ Possible values include: \c{"desktop"}, \c{"pc"}, \c{"phone"}, \c{"server"},
+ and \c{"system"}, which are mapped to the corresponding set of possible values for the
+ \c WINAPI_FAMILY preprocessor define in the Windows SDK. The default is \c undefined,
+ which lets the Windows SDK headers determine the default.
+ \row
+ \li windowsApiAdditionalPartitions
+ \li \c{stringList}
+ \li 1.10
+ \li \c{undefined}
+ \li List of additional Windows API partitions to enable in addition to the ones implicitly
+ enabled by the value of \c cpp.windowsApiFamily (\c WINAPI_FAMILY). This is used when
+ building Windows Store applications. For example, setting \c cpp.windowsApiFamily to
+ \c{"pc"} and \c cpp.windowsApiAdditionalPartitions to \c{["phone"]} will allow you to
+ create Windows Store applications that target all Universal Windows Platform device
+ families ("Universal" apps). Possible values include: \c{"app"}, \c{"desktop"},
+ \c{"pc"}, \c{"phone"}, \c{"server"}, and \c{"system"}, which are mapped to the
+ corresponding set of possible \c WINAPI_PARTITION_* preprocessor defines in the Windows
+ SDK. The default is \c undefined, which lets the Windows SDK headers determine the
+ default partitions based on the value of \c WINAPI_FAMILY (see \c cpp.windowsApiFamily).
+ \row
+ \li requireAppContainer
+ \li \c{bool}
+ \li 1.10
+ \li \c{undefined}
+ \li Whether the generated executable or dynamic-link library \e requires an AppContainer
+ execution environment. This should be used when creating Universal Windows Platform
+ applications.
+ \row
\li minimumWindowsVersion
\li \c{string}
\li 1.0
@@ -745,12 +803,14 @@
\li Whether to always use lipo when combining Mach-O output files on Apple platforms,
even if there is only one CPU architecture. The should not normally need to be changed.
\row
- \li compilerDefines
- \li \c{stringList}
- \li 1.0
+ \li compilerDefinesByLanguage
+ \li \c{string} to \c{string} to \c{string} map
+ \li 1.10
\li \c{undefined}
- \li List of preprocessor macros that are used for all projects that are using the current
- toolchain. User project files usually do not set this property.
+ \li Map of language tags to list of preprocessor macros that are used for all projects that
+ are using the current toolchain. User project files usually do not set this property.
+ \note See \c{cpp.enableCompilerDefinesByLanguage}. This property will not be usable
+ without also setting that one.
\row
\li compilerIncludePaths
\li \c{pathList}
@@ -800,6 +860,14 @@
\li List of distribution-specific library search paths, prioritized after \c{systemLibraryPaths}.
Intended for use by module authors implementing support for new operating systems/distributions.
User project files should not set this property.
+ \row
+ \li enableCompilerDefinesByLanguage
+ \li \c{stringList}
+ \li 1.10
+ \li \c{undefined}
+ \li List of languages (one or more of \c{"c"}, \c{"cpp"}, \c{"objc"}, \c{"objcpp"}) to
+ extract the list of default compiler defines for in \c{cpp.compilerDefinesByLanguage}.
+ Because this has performance implications, no languages are enabled by default.
\endtable
diff --git a/doc/reference/modules/cpufeatures-module.qdoc b/doc/reference/modules/cpufeatures-module.qdoc
new file mode 100644
index 000000000..030424d45
--- /dev/null
+++ b/doc/reference/modules/cpufeatures-module.qdoc
@@ -0,0 +1,191 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \contentspage index.html
+ \page cpufeatures-module.html
+ \ingroup list-of-modules
+
+ \title Module cpufeatures
+ \since 1.10
+ \brief Provides support for fine-tuning CPU features
+
+ The \c cpufeatures module offers properties for enabling or disabling specific CPU features.
+ Use it if you want to override the compiler defaults for a given platform.
+
+ The compiler rules in the \l{Module cpp}{cpp module} evaluate this module's properties
+ and generate matching compiler flags.
+ All properties in this module are of type \c bool and have the following semantics:
+ \list
+ \li The default value \c undefined has no effect on the compiler command line.
+ \li If the value is \c true and the compiler has a flag to enable the feature, that flag
+ is added to the command line if it is applicable to the current architecture.
+ For example, enabling the property \c x86_sse2 would result in the GCC option \c{-msse2}.
+ \li If the value is \c false and the compiler has a flag to disable the feature,
+ that flag is added to the command line if it is applicable to the current architecture.
+ For example, disabling the property \c x86_sse2 would result in the GCC option
+ \c{-no-msse2}.
+ \endlist
+
+ \section1 cpufeatures Properties
+
+ \table
+ \header
+ \li Property
+ \li Type
+ \li Since
+ \li Default
+ \li Description
+ \row
+ \li arm_neon
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use NEON instructions in ARM binaries.
+ \row
+ \li arm_vfpv4
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use VFPv4 instructions in ARM binaries.
+ \row
+ \li mips_dsp
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use DSP instructions in MIPS binaries.
+ \row
+ \li mips_dspr2
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use DSPr2 instructions in MIPS binaries.
+ \row
+ \li x86_avx
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX instructions in x86 binaries.
+ \row
+ \li x86_avx2
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX2 instructions in x86 binaries.
+ \row
+ \li x86_avx512bw
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512-BW instructions in x86 binaries.
+ \row
+ \li x86_avx512cd
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512-CD instructions in x86 binaries.
+ \row
+ \li x86_avx512dq
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512-DQ instructions in x86 binaries.
+ \row
+ \li x86_avx512er
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512-ER instructions in x86 binaries.
+ \row
+ \li x86_avx512f
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512 instructions in x86 binaries.
+ \row
+ \li x86_avx512ifma
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512-IFMA instructions in x86 binaries.
+ \row
+ \li x86_avx512pf
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512-PF instructions in x86 binaries.
+ \row
+ \li x86_avx512vbmi
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512-VBMI instructions in x86 binaries.
+ \row
+ \li x86_avx512vl
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use AVX-512-VL instructions in x86 binaries.
+ \row
+ \li x86_f16c
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use F16C instructions in x86 binaries.
+ \row
+ \li x86_sse2
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use SSE2 instructions in x86 binaries.
+ \row
+ \li x86_sse3
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use SSE3 instructions in x86 binaries.
+ \row
+ \li x86_ssse3
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use SSSE3 instructions in x86 binaries.
+ \row
+ \li x86_sse4_1
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use SSE4.1 instructions in x86 binaries.
+ \row
+ \li x86_sse4_2
+ \li bool
+ \li 1.10
+ \li undefined
+ \li Whether to use SSE4.2 instructions in x86 binaries.
+ \endtable
+
+*/
diff --git a/doc/reference/modules/java-module.qdoc b/doc/reference/modules/java-module.qdoc
index 6b0ba6e8e..663b65ec3 100644
--- a/doc/reference/modules/java-module.qdoc
+++ b/doc/reference/modules/java-module.qdoc
@@ -120,7 +120,7 @@
with native code should add these paths to \c{cpp.includePaths}.
\row
\li jdkPath
- \li path
+ \li string
\li 1.4
\li determined automatically
\li The base path of the Java Development Kit (JDK). This is equivalent to the \c JAVA_HOME
diff --git a/doc/reference/modules/qbs-module.qdoc b/doc/reference/modules/qbs-module.qdoc
index a0260b521..50f9c6d58 100644
--- a/doc/reference/modules/qbs-module.qdoc
+++ b/doc/reference/modules/qbs-module.qdoc
@@ -87,7 +87,6 @@
Possible values include one or more of the following:
\c{"aix"},
\c{"android"},
- \c{"blackberry"},
\c{"bsd"},
\c{"bsd4"},
\c{"bsdi"},
@@ -122,10 +121,7 @@
\c{"unix"},
\c{"unixware"},
\c{"vxworks"},
- \c{"windows"},
- \c{"windowsce"},
- \c{"windowsphone"},
- \c{"winrt"}
+ \c{"windows"}
\row
\li architecture
\li \c{string}
diff --git a/doc/reference/modules/qt-modules.qdoc b/doc/reference/modules/qt-modules.qdoc
index 44b516af7..9f6116683 100644
--- a/doc/reference/modules/qt-modules.qdoc
+++ b/doc/reference/modules/qt-modules.qdoc
@@ -143,7 +143,7 @@
\li quick
\li Qt Quick (2)
\li Provides the \c{Qt Quick} module (Qt Quick 2). For more information,
- see \l {quick Properties}.
+ see \l{quick Properties} and \l{quick File Tags}.
\row
\li qml
\li Qt QML
@@ -481,6 +481,11 @@
\li Default
\li Description
\row
+ \li compilerAvailable
+ \li \c{bool}
+ \li set by \c{qbs-setup-qt}
+ \li Specifies whether the Qt installation contains the Qt Quick compiler.
+ \row
\li qmlDebugging
\li \c{bool}
\li \c{false}
@@ -519,6 +524,41 @@
\li The absolute path to the directory where Qt's QML files are installed.
\endtable
+ The following properties control the generation of QML cache files.
+
+ \table
+ \header
+ \li Property
+ \li Type
+ \li Default
+ \li Description
+ \row
+ \li generateCacheFiles
+ \li \c{bool}
+ \li \c{false}
+ \li Enables the generation of QML cache files.
+ \row
+ \li cachingEnabled
+ \li \c{readonly bool}
+ \li \c{false}
+ \li Is true if \c{generateCacheFiles} is \c{true}
+ and the platform supports QML cache generation.
+ \row
+ \li qmlCacheGenPath
+ \li \c{string}
+ \li set by \c{qbs-setup-qt}
+ \li The absolute path to the qmlcachegen executable.
+ \row
+ \li cacheFilesInstallDir
+ \li \c{string}
+ \li \c{undefined}
+ \li The path to the directory where the cache files are installed.
+ If this property is set then QML cache files are automatically installed.
+ \endtable
+
+ \note If the current value of qbs.architecture is not supported
+ by qmlcachegen then the cache generator rule is disabled.
+
\section2 scxml Properties
@@ -669,12 +709,38 @@
\li Since
\li Description
\row
+ \li \c{"qt.qml.js"}
+ \li \c{*.js}
+ \li 1.10
+ \li QML companion JavaScript files. Source files with this tag serve as input for the QML
+ cache file generator.
+ \row
\li \c{"qt.qml.qml"}
\li \c{*.qml}
\li 1.8
\li Source files with this tag serve as inputs to the QML plugin scanner.
\endtable
+ \section2 quick File Tags
+
+ \table
+ \header
+ \li Tag
+ \li Auto-tagged File Names
+ \li Since
+ \li Description
+ \row
+ \li \c{"qt.quick.qrc"}
+ \li \c{*.qrc}
+ \li 1.10
+ \li Qt resource files with this file tag will be picked up by the
+ Qt Quick compiler rule, and all QML files in the resource will be
+ compiled.
+
+ This file tag will only be added automatically if the Qt Quick
+ compiler is available.
+ \endtable
+
\section2 scxml File Tags
\table
diff --git a/doc/reference/modules/vcs-module.qdoc b/doc/reference/modules/vcs-module.qdoc
new file mode 100644
index 000000000..ed71d0a46
--- /dev/null
+++ b/doc/reference/modules/vcs-module.qdoc
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \contentspage index.html
+ \page vcs-module.html
+ \ingroup list-of-modules
+
+ \title Module vcs
+ \since 1.10
+ \brief Provides support for version control systems
+
+ The \c vcs module provides the current state of the project's repository via the
+ \c repoState property. By default, a \c C header is also generated, allowing for
+ simple retrieval of the repository state directly from within your C/C++ sources.
+ This is useful to embed information into binaries about the exact state of the repository
+ from which they were built. For instance:
+ \code
+ #include <vcs-repo-state.h>
+ #include <iostream>
+
+ int main()
+ {
+ std::cout << "I was built from " << VCS_REPO_STATE << std::endl;
+ }
+ \endcode
+ As you can see in the above code, a header file called \c{vcs-repo-state.h} is created,
+ defining a macro called \c VCS_REPO_STATE which expands to a character constant describing
+ the current state of the repository. For Git, this would be the current HEAD's commit hash.
+
+ \section1 Module Properties
+
+ \table
+ \header
+ \li Property
+ \li Type
+ \li Since
+ \li Default
+ \li Description
+ \row
+ \li headerFileName
+ \li string
+ \li 1.10
+ \li \c{"vcs-repo-state.h"}
+ \li The name of the C header file to be created. Set this to \c undefined if you do
+ not want a header file to be generated.
+ \row
+ \li repoDir
+ \li string
+ \li 1.10
+ \li the top-level project directory (\c{project.sourceDirectory})
+ \li The root directory of the repository.
+ \row
+ \li repoState
+ \li string
+ \li 1.10
+ \li -
+ \li The current state of the repository. For instance, in Git this is the commit hash
+ of the current HEAD.
+ \row
+ \li toolFilePath
+ \li string
+ \li 1.10
+ \li the file name of the version control tool corresponding to \c type
+ \li Set this if the tool has an unusual name in your local installation, or if it is
+ located in a directory that is not in the build environment's \c PATH.
+ \row
+ \li type
+ \li \c{string}
+ \li 1.10
+ \li auto-detected
+ \li The version control system used in the project. Currently, the supported values
+ are \c{"git"} and \c{"svn"}.
+ \endtable
+*/
diff --git a/docker/docker.qbs b/docker/docker.qbs
new file mode 100644
index 000000000..98738a9f4
--- /dev/null
+++ b/docker/docker.qbs
@@ -0,0 +1,35 @@
+import qbs
+
+Project {
+ Product {
+ Depends { name: "docker"; required: false }
+
+ name: "qbs-docker-stretch"
+ type: ["docker.docker-image"]
+ builtByDefault: false
+ condition: docker.present
+
+ docker.imageTag: "qbsbuild/qbsdev:stretch"
+
+ files: [
+ "stretch/Dockerfile",
+ "stretch/qtifwsilent.qs",
+ ]
+ }
+
+ Product {
+ Depends { name: "docker"; required: false }
+
+ name: "qbs-docker-windowsservercore"
+ type: ["docker.docker-image"]
+ builtByDefault: false
+ condition: docker.present
+
+ docker.imageTag: "qbsbuild/qbsdev:windowsservercore"
+
+ files: [
+ "windowsservercore/Dockerfile",
+ "windowsservercore/qtifwsilent.qs",
+ ]
+ }
+}
diff --git a/examples/app-and-lib/app/app.qbs b/examples/app-and-lib/app/app.qbs
index ebc169753..9b9c8809e 100644
--- a/examples/app-and-lib/app/app.qbs
+++ b/examples/app-and-lib/app/app.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs 1.0
diff --git a/examples/app-and-lib/app/main.cpp b/examples/app-and-lib/app/main.cpp
index 37c7ab03e..d5a1a183c 100644
--- a/examples/app-and-lib/app/main.cpp
+++ b/examples/app-and-lib/app/main.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/app-and-lib/app_and_lib.qbs b/examples/app-and-lib/app_and_lib.qbs
index e214055b5..baa42307f 100644
--- a/examples/app-and-lib/app_and_lib.qbs
+++ b/examples/app-and-lib/app_and_lib.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs 1.0
diff --git a/examples/app-and-lib/lib/lib.cpp b/examples/app-and-lib/lib/lib.cpp
index eacbeaeca..db312e2d2 100644
--- a/examples/app-and-lib/lib/lib.cpp
+++ b/examples/app-and-lib/lib/lib.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/app-and-lib/lib/lib.h b/examples/app-and-lib/lib/lib.h
index 1117ef1ef..21d83e352 100644
--- a/examples/app-and-lib/lib/lib.h
+++ b/examples/app-and-lib/lib/lib.h
@@ -1,11 +1,11 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -14,24 +14,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/app-and-lib/lib/lib.qbs b/examples/app-and-lib/lib/lib.qbs
index 0e6679e78..06e6365ce 100644
--- a/examples/app-and-lib/lib/lib.qbs
+++ b/examples/app-and-lib/lib/lib.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs 1.0
diff --git a/examples/cocoa-application/CocoaApplication.qbs b/examples/cocoa-application/CocoaApplication.qbs
index d0d2affc6..472758764 100644
--- a/examples/cocoa-application/CocoaApplication.qbs
+++ b/examples/cocoa-application/CocoaApplication.qbs
@@ -1,12 +1,23 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
** Copyright (C) 2015 Petroules Corporation.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -17,8 +28,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -34,6 +45,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs
diff --git a/examples/cocoa-application/CocoaApplication/AppDelegate.h b/examples/cocoa-application/CocoaApplication/AppDelegate.h
index 664a0baef..563a1b2f9 100644
--- a/examples/cocoa-application/CocoaApplication/AppDelegate.h
+++ b/examples/cocoa-application/CocoaApplication/AppDelegate.h
@@ -1,12 +1,12 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Petroules Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -15,24 +15,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/cocoa-application/app.qbs b/examples/cocoa-application/app.qbs
index 5d7b1cefe..f51f94e8b 100644
--- a/examples/cocoa-application/app.qbs
+++ b/examples/cocoa-application/app.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs
@@ -68,6 +81,13 @@ CppApplication {
}
Group {
+ name: "Xcode Project"
+ files: [
+ "CocoaApplication.xcodeproj/project.pbxproj"
+ ]
+ }
+
+ Group {
files: ["CocoaApplication/CocoaApplication-Prefix.pch"]
fileTags: ["objc_pch_src"]
}
diff --git a/examples/cocoa-application/dmg.qbs b/examples/cocoa-application/dmg.qbs
index e8e0a923a..610c5d2a3 100644
--- a/examples/cocoa-application/dmg.qbs
+++ b/examples/cocoa-application/dmg.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs
diff --git a/examples/cocoa-touch-application/CocoaTouchApplication.qbs b/examples/cocoa-touch-application/CocoaTouchApplication.qbs
index e2d7c9bc2..9a1046611 100644
--- a/examples/cocoa-touch-application/CocoaTouchApplication.qbs
+++ b/examples/cocoa-touch-application/CocoaTouchApplication.qbs
@@ -1,12 +1,23 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
** Copyright (C) 2015 Petroules Corporation.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -17,8 +28,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -34,6 +45,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs 1.0
@@ -77,6 +90,13 @@ CppApplication {
}
Group {
+ name: "Xcode Project"
+ files: [
+ "CocoaTouchApplication.xcodeproj/project.pbxproj"
+ ]
+ }
+
+ Group {
files: ["CocoaTouchApplication/CocoaTouchApplication-Prefix.pch"]
fileTags: ["objc_pch_src"]
}
diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h b/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h
index b5c92ff94..a4b05b986 100644
--- a/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h
+++ b/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h
@@ -1,12 +1,12 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Petroules Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -15,24 +15,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h b/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h
index 4a71f0124..fd057606b 100644
--- a/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h
+++ b/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h
@@ -1,12 +1,12 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Petroules Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -15,24 +15,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h b/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h
index 964ce3d93..27758ce33 100644
--- a/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h
+++ b/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h
@@ -1,12 +1,12 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Petroules Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -15,24 +15,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/code-generator/code-generator.qbs b/examples/code-generator/code-generator.qbs
index b9aef12b6..df15dec5e 100644
--- a/examples/code-generator/code-generator.qbs
+++ b/examples/code-generator/code-generator.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs
diff --git a/examples/code-generator/hwgen.cpp b/examples/code-generator/hwgen.cpp
index 2dd1aa777..1e2556648 100644
--- a/examples/code-generator/hwgen.cpp
+++ b/examples/code-generator/hwgen.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/collidingmice/collidingmice.qbs b/examples/collidingmice/collidingmice.qbs
index 9be12f53e..0b06eff2c 100644
--- a/examples/collidingmice/collidingmice.qbs
+++ b/examples/collidingmice/collidingmice.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs 1.0
@@ -47,6 +60,7 @@ Application {
}
files : [
+ "images/cheese.jpg",
"main.cpp",
"mouse.cpp",
"mouse.h",
diff --git a/examples/collidingmice/main.cpp b/examples/collidingmice/main.cpp
index 7bd074ef2..e5a12300b 100644
--- a/examples/collidingmice/main.cpp
+++ b/examples/collidingmice/main.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/collidingmice/mouse.cpp b/examples/collidingmice/mouse.cpp
index 4485e0ae7..c3373501c 100644
--- a/examples/collidingmice/mouse.cpp
+++ b/examples/collidingmice/mouse.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/collidingmice/mouse.h b/examples/collidingmice/mouse.h
index c04e60e50..4e6958c78 100644
--- a/examples/collidingmice/mouse.h
+++ b/examples/collidingmice/mouse.h
@@ -1,11 +1,11 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -14,24 +14,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/compiled-qml/MainForm.ui.qml b/examples/compiled-qml/MainForm.ui.qml
new file mode 100644
index 000000000..0b1653ebf
--- /dev/null
+++ b/examples/compiled-qml/MainForm.ui.qml
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.3
+
+Rectangle {
+ property alias mouseArea: mouseArea
+
+ width: 360
+ height: 360
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ }
+
+ Image {
+ anchors.centerIn: parent
+ source: "cheese.jpg"
+ }
+}
diff --git a/examples/compiled-qml/cheese.jpg b/examples/compiled-qml/cheese.jpg
new file mode 100644
index 000000000..dea5795fd
--- /dev/null
+++ b/examples/compiled-qml/cheese.jpg
Binary files differ
diff --git a/examples/compiled-qml/main.cpp b/examples/compiled-qml/main.cpp
new file mode 100644
index 000000000..27074068f
--- /dev/null
+++ b/examples/compiled-qml/main.cpp
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+ return app.exec();
+}
diff --git a/examples/compiled-qml/main.qml b/examples/compiled-qml/main.qml
new file mode 100644
index 000000000..4b7c4dc77
--- /dev/null
+++ b/examples/compiled-qml/main.qml
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.4
+import QtQuick.Window 2.2
+
+Window {
+ visible: true
+ MainForm {
+ anchors.fill: parent
+ mouseArea.onClicked: {
+ Qt.quit();
+ }
+ }
+}
diff --git a/examples/compiled-qml/myapp.qbs b/examples/compiled-qml/myapp.qbs
new file mode 100644
index 000000000..3a900fac9
--- /dev/null
+++ b/examples/compiled-qml/myapp.qbs
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import qbs
+
+CppApplication {
+ Depends { name: "Qt.quick" }
+
+ files: [
+ "cheese.jpg",
+ "main.cpp",
+ "qml.qrc"
+ ]
+
+ Group {
+ name: "QML Files"
+ files: ["*.qml"]
+ }
+}
diff --git a/examples/compiled-qml/qml.qrc b/examples/compiled-qml/qml.qrc
new file mode 100644
index 000000000..deb314e51
--- /dev/null
+++ b/examples/compiled-qml/qml.qrc
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ <file>MainForm.ui.qml</file>
+ <file>cheese.jpg</file>
+ </qresource>
+</RCC>
diff --git a/examples/examples.qbs b/examples/examples.qbs
index fa9b8809a..0e4e5052c 100644
--- a/examples/examples.qbs
+++ b/examples/examples.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs
@@ -44,6 +57,7 @@ Project {
"cocoa-touch-application/CocoaTouchApplication.qbs",
"code-generator/code-generator.qbs",
"collidingmice/collidingmice.qbs",
+ "compiled-qml/myapp.qbs",
"helloworld-complex/hello.qbs",
"helloworld-minimal/hello.qbs",
"helloworld-qt/hello.qbs",
diff --git a/examples/helloworld-complex/hello.qbs b/examples/helloworld-complex/hello.qbs
index a9f7b8fee..725424e49 100644
--- a/examples/helloworld-complex/hello.qbs
+++ b/examples/helloworld-complex/hello.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs 1.0
diff --git a/examples/helloworld-complex/src/foo.cpp b/examples/helloworld-complex/src/foo.cpp
index 681e8dce8..74c5bf7b4 100644
--- a/examples/helloworld-complex/src/foo.cpp
+++ b/examples/helloworld-complex/src/foo.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/helloworld-complex/src/foo.h b/examples/helloworld-complex/src/foo.h
index eda499d77..abeef38bd 100644
--- a/examples/helloworld-complex/src/foo.h
+++ b/examples/helloworld-complex/src/foo.h
@@ -1,11 +1,11 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -14,24 +14,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/helloworld-complex/src/main.cpp b/examples/helloworld-complex/src/main.cpp
index a86ef43d5..f3dbcc4f7 100644
--- a/examples/helloworld-complex/src/main.cpp
+++ b/examples/helloworld-complex/src/main.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/helloworld-complex/src/specialfeature.cpp b/examples/helloworld-complex/src/specialfeature.cpp
index a2e7c5a82..63f5fd769 100644
--- a/examples/helloworld-complex/src/specialfeature.cpp
+++ b/examples/helloworld-complex/src/specialfeature.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/helloworld-complex/src/specialfeature.h b/examples/helloworld-complex/src/specialfeature.h
index d209c37d1..9e1cfaaa9 100644
--- a/examples/helloworld-complex/src/specialfeature.h
+++ b/examples/helloworld-complex/src/specialfeature.h
@@ -1,11 +1,11 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -14,24 +14,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/helloworld-minimal/hello.qbs b/examples/helloworld-minimal/hello.qbs
index aa5399780..5cad9f4bf 100644
--- a/examples/helloworld-minimal/hello.qbs
+++ b/examples/helloworld-minimal/hello.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs
diff --git a/examples/helloworld-minimal/main.cpp b/examples/helloworld-minimal/main.cpp
index 0c2d35ca3..6d8ac039b 100644
--- a/examples/helloworld-minimal/main.cpp
+++ b/examples/helloworld-minimal/main.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/helloworld-qt/hello.qbs b/examples/helloworld-qt/hello.qbs
index de58d3a86..4c6e070af 100644
--- a/examples/helloworld-qt/hello.qbs
+++ b/examples/helloworld-qt/hello.qbs
@@ -1,11 +1,22 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of Qbs.
**
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
@@ -16,8 +27,8 @@
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
-** * Neither the name of The Qt Company Ltd and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
@@ -33,6 +44,8 @@
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
+** $QT_END_LICENSE$
+**
****************************************************************************/
import qbs
diff --git a/examples/helloworld-qt/main.cpp b/examples/helloworld-qt/main.cpp
index 1a128d4c8..217fc12d0 100644
--- a/examples/helloworld-qt/main.cpp
+++ b/examples/helloworld-qt/main.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/install-bundle/coreutils.cpp b/examples/install-bundle/coreutils.cpp
index f269d0800..88e9e4259 100644
--- a/examples/install-bundle/coreutils.cpp
+++ b/examples/install-bundle/coreutils.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/examples/install-bundle/coreutils.h b/examples/install-bundle/coreutils.h
index fcf21a33a..faefc125d 100644
--- a/examples/install-bundle/coreutils.h
+++ b/examples/install-bundle/coreutils.h
@@ -1,11 +1,11 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
-** $QT_BEGIN_LICENSE:LGPL$
+** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
@@ -14,24 +14,35 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
diff --git a/examples/install-bundle/main.cpp b/examples/install-bundle/main.cpp
index b3d807009..4d22c8899 100644
--- a/examples/install-bundle/main.cpp
+++ b/examples/install-bundle/main.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of Qbs.
+** This file is part of the examples of Qbs.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
diff --git a/qbs-resources/modules/docker/docker.qbs b/qbs-resources/modules/docker/docker.qbs
new file mode 100644
index 000000000..422b1a8f5
--- /dev/null
+++ b/qbs-resources/modules/docker/docker.qbs
@@ -0,0 +1,51 @@
+import qbs
+import qbs.FileInfo
+import qbs.Probes
+import qbs.Utilities
+
+Module {
+ Probes.BinaryProbe {
+ id: dockercli
+ names: ["docker"]
+ platformPaths: qbs.hostOS.contains("unix") ? ["/usr/bin", "/usr/local/bin"] : []
+ }
+
+ property string dockerFilePath: dockercli.filePath
+ property string imageTag
+ property stringList buildFlags
+
+ FileTagger {
+ patterns: ["Dockerfile"]
+ fileTags: ["docker.dockerfile"]
+ }
+
+ Rule {
+ inputs: ["docker.dockerfile"]
+
+ Artifact {
+ // Let Docker handle the dependency management
+ filePath: FileInfo.joinPaths(product.buildDirectory,
+ Utilities.getHash(input.filePath), ".docker-image-dummy")
+ fileTags: ["docker.docker-image"]
+ }
+
+ prepare: {
+ var args = ["build"];
+ var tag = product.docker.imageTag;
+ if (tag)
+ args.push("-t", tag);
+ Array.prototype.push.apply(args, product.docker.buildFlags);
+ args.push(".");
+ var cmd = new Command(product.docker.dockerFilePath, args);
+ cmd.workingDirectory = FileInfo.path(input.filePath);
+ cmd.description = "building docker image "
+ + FileInfo.fileName(cmd.workingDirectory) + (tag ? " (" + tag + ")" : "");
+ return [cmd];
+ }
+ }
+
+ validate: {
+ if (!dockerFilePath)
+ throw ModUtils.ModuleError("Could not find Docker.");
+ }
+}
diff --git a/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs b/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs
index 0e93f81b4..6e510a786 100644
--- a/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs
+++ b/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs
@@ -11,6 +11,7 @@ Module {
property string importLibInstallDir: libDirName
property string libexecInstallDir: qbs.targetOS.contains("windows") ? appInstallDir
: "libexec/qbs"
+ property bool installManPage: qbs.targetOS.contains("unix")
property bool installHtml: true
property bool installQch: false
property string docInstallDir: "share/doc/qbs/html"
diff --git a/qbs.qbs b/qbs.qbs
index 8146d5ab6..e9d5d266d 100644
--- a/qbs.qbs
+++ b/qbs.qbs
@@ -9,8 +9,11 @@ Project {
references: [
"doc/doc.qbs",
+ "doc/man/man.qbs",
+ "docker/docker.qbs",
"share/share.qbs",
"src/src.qbs",
+ "scripts/scripts.qbs",
"tests/auto/auto.qbs",
"tests/fuzzy-test/fuzzy-test.qbs",
"tests/benchmarker/benchmarker.qbs",
@@ -30,7 +33,7 @@ Project {
Product {
name: "qmake project files for qbs"
- files: ["**/*.pr[io]"]
+ files: ["**/*.pr[io]", "sync.profile"]
}
AutotestRunner {
diff --git a/scripts/scripts.qbs b/scripts/scripts.qbs
new file mode 100644
index 000000000..9a631ed10
--- /dev/null
+++ b/scripts/scripts.qbs
@@ -0,0 +1,9 @@
+import qbs
+
+Product {
+ name: "qbs dev scripts"
+ files: [
+ "*.bat",
+ "*.sh",
+ ]
+}
diff --git a/scripts/update-dmgbuild.sh b/scripts/update-dmgbuild.sh
new file mode 100755
index 000000000..01587677e
--- /dev/null
+++ b/scripts/update-dmgbuild.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+set -e
+
+#############################################################################
+##
+## Copyright (C) 2016 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of Qbs.
+##
+## $QT_BEGIN_LICENSE:LGPL$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU Lesser General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU Lesser
+## General Public License version 3 as published by the Free Software
+## Foundation and appearing in the file LICENSE.LGPL3 included in the
+## packaging of this file. Please review the following information to
+## ensure the GNU Lesser General Public License version 3 requirements
+## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 2.0 or (at your option) the GNU General
+## Public license version 3 or any later version approved by the KDE Free
+## Qt Foundation. The licenses are as published by the Free Software
+## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-2.0.html and
+## https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+python_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../src/3rdparty/python"
+repos=(biplist.git@v1.0.2 dmgbuild.git@v1.3.1 ds_store@v1.1.2 mac_alias.git@v2.0.6)
+for repo in "${repos[@]}" ; do
+ pip install -U --isolated "--prefix=$python_dir" --no-binary :all: --no-compile --no-deps \
+ "git+git://github.com/qbs/$repo"
+done
+rm "$python_dir/lib/python2.7/site-packages/dmgbuild/resources/"*.tiff
diff --git a/share/qbs/modules/bundle/update-specs.sh b/scripts/update-xcspecs.sh
index 08e862d03..2c046f385 100755
--- a/share/qbs/modules/bundle/update-specs.sh
+++ b/scripts/update-xcspecs.sh
@@ -1,4 +1,5 @@
#!/bin/bash
+set -e
#############################################################################
##
@@ -41,9 +42,11 @@
# Update build specs from Xcode - this script should be run when new Xcode releases are made.
specs_dir="$(xcrun --sdk macosx --show-sdk-platform-path)/Developer/Library/Xcode/Specifications"
+specs_out_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../share/qbs/modules/bundle"
spec_files=("MacOSX Package Types.xcspec" "MacOSX Product Types.xcspec")
for spec_file in "${spec_files[@]}" ; do
- printf "%s\n" "$(plutil -convert json -r -o - "$specs_dir/$spec_file")" > "${spec_file// /-}"
+ printf "%s\\n" "$(plutil -convert json -r -o - "$specs_dir/$spec_file")" > \
+ "$specs_out_dir/${spec_file// /-}"
done
xcode_version="$(/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' \
"$(xcode-select --print-path)/../Info.plist")"
diff --git a/share/qbs/imports/qbs/PathTools/path-tools.js b/share/qbs/imports/qbs/PathTools/path-tools.js
index 68c37f568..b2cb63e39 100644
--- a/share/qbs/imports/qbs/PathTools/path-tools.js
+++ b/share/qbs/imports/qbs/PathTools/path-tools.js
@@ -31,14 +31,20 @@
var FileInfo = require("qbs.FileInfo");
function _bundleExecutableTemporaryFilePath(product, variantSuffix) {
+ if (variantSuffix === undefined)
+ variantSuffix = product.cpp.variantSuffix;
return ".tmp/" + FileInfo.fileName(bundleExecutableFilePath(product, variantSuffix));
}
function bundleExecutableFilePath(product, variantSuffix) {
+ if (variantSuffix === undefined)
+ variantSuffix = product.cpp.variantSuffix;
return product.moduleProperty("bundle", "executablePath") + (variantSuffix || "");
}
function applicationFilePath(product, variantSuffix) {
+ if (variantSuffix === undefined)
+ variantSuffix = product.cpp.variantSuffix;
if (product.moduleProperty("bundle", "isBundle"))
return _bundleExecutableTemporaryFilePath(product, variantSuffix);
@@ -48,6 +54,8 @@ function applicationFilePath(product, variantSuffix) {
}
function loadableModuleFilePath(product, variantSuffix) {
+ if (variantSuffix === undefined)
+ variantSuffix = product.cpp.variantSuffix;
if (product.moduleProperty("bundle", "isBundle"))
return _bundleExecutableTemporaryFilePath(product, variantSuffix);
@@ -57,6 +65,8 @@ function loadableModuleFilePath(product, variantSuffix) {
}
function staticLibraryFilePath(product, variantSuffix) {
+ if (variantSuffix === undefined)
+ variantSuffix = product.cpp.variantSuffix;
if (product.moduleProperty("bundle", "isBundle"))
return _bundleExecutableTemporaryFilePath(product, variantSuffix);
@@ -66,6 +76,8 @@ function staticLibraryFilePath(product, variantSuffix) {
}
function dynamicLibraryFilePath(product, variantSuffix, version, maxParts) {
+ if (variantSuffix === undefined)
+ variantSuffix = product.cpp.variantSuffix;
if (product.moduleProperty("bundle", "isBundle"))
return _bundleExecutableTemporaryFilePath(product, variantSuffix);
@@ -123,6 +135,7 @@ function linkerOutputFilePath(fileTag, product, variantSuffix, version, maxParts
function importLibraryFilePath(product) {
return product.moduleProperty("cpp", "dynamicLibraryPrefix")
+ product.targetName
+ + (product.cpp.variantSuffix || "")
+ product.moduleProperty("cpp", "dynamicLibraryImportSuffix");
}
diff --git a/share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs b/share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs
index 82a60bccd..505675354 100644
--- a/share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs
+++ b/share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2015 Jake Petroules.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qbs.
@@ -63,6 +64,20 @@ PathProbe {
property string ndkVersion
configure: {
+ function readFileContent(filePath) {
+ var result = null;
+ if (!File.exists(filePath))
+ return result;
+ try {
+ var tf = new TextFile(filePath, TextFile.ReadOnly);
+ result = tf.readAll();
+ } finally {
+ if (tf)
+ tf.close();
+ }
+ return result;
+ }
+
var i, j, allPaths = (environmentPaths || []).concat(platformPaths || []);
for (i in allPaths) {
var platforms = [];
@@ -82,9 +97,9 @@ PathProbe {
File.Dirs | File.NoDotAndDotDot);
// NDK r11 and above
- var tf = new TextFile(path + "/source.properties", TextFile.ReadOnly);
- try {
- var lines = tf.readAll().trim().split(/\r?\n/g).filter(function (line) {
+ var content = readFileContent(path + "/source.properties");
+ if (content) {
+ var lines = content.trim().split(/\r?\n/g).filter(function (line) {
return line.length > 0;
});
for (var l = 0; l < lines.length; ++l) {
@@ -95,21 +110,21 @@ PathProbe {
return;
}
}
- } finally {
- tf.close();
}
// NDK r10 and below
- tf = new TextFile(path + "/RELEASE.txt", TextFile.ReadOnly);
- try {
- var m = tf.readAll().trim().match(/^r([0-9]+[a-z]?)( \(64-bit\))?$/);
+ var releaseTextFileCandidates = ["RELEASE.txt", "RELEASE.TXT"]
+ .map(function(v) { return FileInfo.joinPaths(path, v); })
+ .filter(File.exists);
+ content = releaseTextFileCandidates.length
+ ? readFileContent(releaseTextFileCandidates[0]) : null;
+ if (content) {
+ var m = content.trim().match(/^r([0-9]+[a-z]?).*/);
if (m) {
ndkVersion = m[1];
found = true;
return;
}
- } finally {
- tf.close();
}
}
}
diff --git a/share/qbs/imports/qbs/Probes/GccProbe.qbs b/share/qbs/imports/qbs/Probes/GccProbe.qbs
index 0ffe10f4d..627f9b468 100644
--- a/share/qbs/imports/qbs/Probes/GccProbe.qbs
+++ b/share/qbs/imports/qbs/Probes/GccProbe.qbs
@@ -35,7 +35,8 @@ import "../../../modules/cpp/gcc.js" as Gcc
PathProbe {
// Inputs
- property string compilerFilePath
+ property var compilerFilePathByLanguage
+ property stringList enableDefinesByLanguage
property stringList flags: []
property var environment
@@ -49,15 +50,40 @@ PathProbe {
property stringList includePaths
property stringList libraryPaths
property stringList frameworkPaths
+ property var compilerDefinesByLanguage
configure: {
- if (!File.exists(compilerFilePath)) {
- found = false;
- return;
+ compilerDefinesByLanguage = {};
+ var languages = enableDefinesByLanguage;
+ if (!languages || languages.length === 0)
+ languages = ["c"];
+ for (var i = 0; i < languages.length; ++i) {
+ var fp = compilerFilePathByLanguage[languages[i]];
+ if (fp && File.exists(fp)) {
+ try {
+ compilerDefinesByLanguage[languages[i]] = Gcc.dumpMacros(environment, fp,
+ flags, _nullDevice,
+ languages[i]);
+ } catch (e) {
+ // Only throw errors when determining the compiler defines for the C language;
+ // for other languages we presume it is an indication that the language is not
+ // installed (as is typically the case for Objective-C/C++ on non-Apple systems)
+ if (languages[i] === "c")
+ throw e;
+ }
+ } else if (languages[i] === "c") {
+ found = false;
+ return;
+ }
}
- var macros = Gcc.dumpMacros(environment, compilerFilePath, flags, _nullDevice);
- var defaultPaths = Gcc.dumpDefaultPaths(environment, compilerFilePath, flags, _nullDevice,
+ var macros = compilerDefinesByLanguage["c"]
+ || compilerDefinesByLanguage["cpp"]
+ || compilerDefinesByLanguage["objc"]
+ || compilerDefinesByLanguage["objcpp"];
+ var defaultPaths = Gcc.dumpDefaultPaths(environment, compilerFilePathByLanguage["cpp"] ||
+ compilerFilePathByLanguage["c"],
+ flags, _nullDevice,
_pathListSeparator, _sysroot);
found = !!macros && !!defaultPaths;
diff --git a/share/qbs/imports/qbs/Probes/JdkProbe.qbs b/share/qbs/imports/qbs/Probes/JdkProbe.qbs
index aaa03683b..9eaa3c36c 100644
--- a/share/qbs/imports/qbs/Probes/JdkProbe.qbs
+++ b/share/qbs/imports/qbs/Probes/JdkProbe.qbs
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2015 Jake Petroules.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qbs.
@@ -30,9 +31,6 @@
import qbs
import qbs.Environment
-import qbs.File
-import qbs.FileInfo
-import qbs.Process
import "../../../modules/java/utils.js" as JavaUtils
PathProbe {
@@ -49,13 +47,8 @@ PathProbe {
"/usr/lib/jvm/default" // Arch
]
- // Outputs
- property var version
-
configure: {
path = JavaUtils.findJdkPath(hostOS, architecture, environmentPaths, platformPaths);
- if (path)
- version = JavaUtils.findJdkVersion(FileInfo.joinPaths(path, "bin", "javac"));
- found = path && version;
+ found = !!path;
}
}
diff --git a/share/qbs/imports/qbs/Probes/JdkVersionProbe.qbs b/share/qbs/imports/qbs/Probes/JdkVersionProbe.qbs
new file mode 100644
index 000000000..299ebbb01
--- /dev/null
+++ b/share/qbs/imports/qbs/Probes/JdkVersionProbe.qbs
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+import qbs
+import "../../../modules/java/utils.js" as JavaUtils
+
+Probe {
+ // Inputs
+ property string javac
+
+ // Outputs
+ property var version
+
+ configure: {
+ version = JavaUtils.findJdkVersion(javac);
+ found = !!version;
+ }
+}
diff --git a/share/qbs/imports/qbs/Probes/MsvcProbe.qbs b/share/qbs/imports/qbs/Probes/MsvcProbe.qbs
index d62a9e1ea..700c579f5 100644
--- a/share/qbs/imports/qbs/Probes/MsvcProbe.qbs
+++ b/share/qbs/imports/qbs/Probes/MsvcProbe.qbs
@@ -37,6 +37,7 @@ import qbs.Utilities
PathProbe {
// Inputs
property string compilerFilePath
+ property stringList enableDefinesByLanguage
property string preferredArchitecture
// Outputs
@@ -46,12 +47,28 @@ PathProbe {
property int versionPatch
property stringList includePaths
property var buildEnv
+ property var compilerDefinesByLanguage
configure: {
- var info = Utilities.msvcCompilerInfo(compilerFilePath);
- found = !!info && !!info.macros && !!info.buildEnvironment;
+ var languages = enableDefinesByLanguage;
+ if (!languages || languages.length === 0)
+ languages = ["c"];
- var macros = info.macros;
+ var info = languages.contains("c")
+ ? Utilities.msvcCompilerInfo(compilerFilePath, "c") : {};
+ var infoCpp = languages.contains("cpp")
+ ? Utilities.msvcCompilerInfo(compilerFilePath, "cpp") : {};
+ found = (!languages.contains("c") ||
+ (!!info && !!info.macros && !!info.buildEnvironment))
+ && (!languages.contains("cpp") ||
+ (!!infoCpp && !!infoCpp.macros && !!infoCpp.buildEnvironment));
+
+ compilerDefinesByLanguage = {
+ "c": info.macros,
+ "cpp": infoCpp.macros,
+ };
+
+ var macros = info.macros || infoCpp.macros;
architecture = ModUtils.guessArchitecture(macros);
var ver = macros["_MSC_FULL_VER"];
@@ -60,7 +77,7 @@ PathProbe {
versionMinor = parseInt(ver.substr(2, 2), 10);
versionPatch = parseInt(ver.substr(4), 10);
- buildEnv = info.buildEnvironment;
+ buildEnv = info.buildEnvironment || infoCpp.buildEnvironment;
var clParentDir = FileInfo.joinPaths(FileInfo.path(compilerFilePath), "..");
var inclPath = FileInfo.joinPaths(clParentDir, "INCLUDE");
if (!File.exists(inclPath))
diff --git a/share/qbs/imports/qbs/WindowsUtils/windows-utils.js b/share/qbs/imports/qbs/WindowsUtils/windows-utils.js
index 136fb3cff..e4b8487e4 100644
--- a/share/qbs/imports/qbs/WindowsUtils/windows-utils.js
+++ b/share/qbs/imports/qbs/WindowsUtils/windows-utils.js
@@ -28,6 +28,27 @@
**
****************************************************************************/
+function winapiFamilyDefine(name) {
+ return {
+ "desktop": "DESKTOP_APP",
+ "phone": "PHONE_APP",
+ "pc": "PC_APP",
+ "server": "SERVER",
+ "system": "SYSTEM"
+ }[name];
+}
+
+function winapiPartitionDefine(name) {
+ return {
+ "app": "APP",
+ "desktop": "DESKTOP",
+ "phone": "PHONE_APP",
+ "pc": "PC_APP",
+ "server": "SERVER",
+ "system": "SYSTEM"
+ }[name];
+}
+
function characterSetDefines(charset) {
var defines = [];
if (charset === "unicode")
diff --git a/share/qbs/imports/qbs/base/AndroidApk.qbs b/share/qbs/imports/qbs/base/AndroidApk.qbs
index c6f3ce7de..8ba7ff049 100644
--- a/share/qbs/imports/qbs/base/AndroidApk.qbs
+++ b/share/qbs/imports/qbs/base/AndroidApk.qbs
@@ -29,6 +29,7 @@
****************************************************************************/
import qbs
+import qbs.File
import qbs.FileInfo
Product {
@@ -44,7 +45,10 @@ Product {
property path resourcesDir: FileInfo.joinPaths(sourceSetDir, "res")
property path assetsDir: FileInfo.joinPaths(sourceSetDir, "assets")
property path sourcesDir: FileInfo.joinPaths(sourceSetDir, legacyLayout ? "src" : "java")
- property path manifestFile: FileInfo.joinPaths(sourceSetDir, "AndroidManifest.xml")
+ property path manifestFile: defaultManifestFile
+
+ readonly property path defaultManifestFile: FileInfo.joinPaths(sourceSetDir,
+ "AndroidManifest.xml")
Group {
name: "java sources"
@@ -72,6 +76,9 @@ Product {
Group {
name: "manifest"
condition: product.automaticSources
- files: [manifestFile]
+ fileTags: ["android.manifest"]
+ files: manifestFile && manifestFile !== defaultManifestFile
+ ? [manifestFile]
+ : (File.exists(defaultManifestFile) ? [defaultManifestFile] : [])
}
}
diff --git a/share/qbs/modules/Android/sdk/sdk.qbs b/share/qbs/modules/Android/sdk/sdk.qbs
index bca52a203..9afc423a0 100644
--- a/share/qbs/modules/Android/sdk/sdk.qbs
+++ b/share/qbs/modules/Android/sdk/sdk.qbs
@@ -29,6 +29,7 @@
****************************************************************************/
import qbs
+import qbs.Environment
import qbs.File
import qbs.FileInfo
import qbs.ModUtils
@@ -77,6 +78,7 @@ Module {
property path buildToolsDir: FileInfo.joinPaths(sdkDir, "build-tools", buildToolsVersion)
property path aaptFilePath: FileInfo.joinPaths(buildToolsDir, "aapt")
+ property path apksignerFilePath: FileInfo.joinPaths(buildToolsDir, "apksigner")
property path aidlFilePath: FileInfo.joinPaths(buildToolsDir, "aidl")
property path dxFilePath: FileInfo.joinPaths(buildToolsDir, "dx")
property path zipalignFilePath: FileInfo.joinPaths(buildToolsDir, "zipalign")
@@ -84,7 +86,13 @@ Module {
"android.jar")
property path generatedJavaFilesBaseDir: FileInfo.joinPaths(product.buildDirectory, "gen")
property path generatedJavaFilesDir: FileInfo.joinPaths(generatedJavaFilesBaseDir,
- product.packageName.split('.').join('/'))
+ (product.packageName || "").split('.').join('/'))
+ property string apkContentsDir: FileInfo.joinPaths(product.buildDirectory, "bin")
+ property string debugKeyStorePath: FileInfo.joinPaths(
+ Environment.getEnv(qbs.hostOS.contains("windows")
+ ? "USERPROFILE" : "HOME"),
+ ".android", "debug.keystore")
+ property bool useApksigner: Utilities.versionCompare(buildToolsVersion, "24.0.3") >= 0
Depends { name: "java" }
java.languageVersion: platformJavaVersion
@@ -101,6 +109,39 @@ Module {
fileTags: ["android.aidl"]
}
+ FileTagger {
+ patterns: ["*.keystore"]
+ fileTags: ["android.keystore"]
+ }
+
+ // Typically there is a debug keystore in ~/.android/debug.keystore which gets created
+ // by the native build tools the first time a build is done. However, we don't want to create it
+ // ourselves, because writing to a location outside the qbs build directory is both polluting
+ // and has the potential for race conditions. So we'll instruct the user what to do.
+ Group {
+ name: "Android debug keystore"
+ files: {
+ if (!File.exists(Android.sdk.debugKeyStorePath)) {
+ throw ModUtils.ModuleError("Could not find an Android debug keystore at " +
+ Android.sdk.debugKeyStorePath + ". " +
+ "If you are developing for Android on this machine for the first time and " +
+ "have never built an application using the native Gradle / Android Studio " +
+ "tooling, this is normal. You must create the debug keystore now using the " +
+ "following command, in order to continue:\n\n" +
+ SdkUtils.createDebugKeyStoreCommandString(java.keytoolFilePath,
+ Android.sdk.debugKeyStorePath) +
+ "\n\n" +
+ "See the following URL for more information: " +
+ "https://developer.android.com/studio/publish/app-signing.html#debug-mode");
+ }
+ return [Android.sdk.debugKeyStorePath];
+ }
+ fileTags: ["android.keystore"]
+ }
+
+ Parameter {
+ property bool embedJar: true
+ }
Rule {
inputs: ["android.aidl"]
@@ -122,13 +163,9 @@ Module {
multiplex: true
inputs: ["android.resources", "android.assets", "android.manifest"]
- outputFileTags: ["android.ap_", "java.java"]
+ outputFileTags: ["java.java"]
outputArtifacts: {
- var artifacts = [{
- filePath: product.name + ".ap_",
- fileTags: ["android.ap_"]
- }];
-
+ var artifacts = [];
var resources = inputs["android.resources"];
if (resources && resources.length) {
artifacts.push({
@@ -142,28 +179,13 @@ Module {
return artifacts;
}
- prepare: {
- var manifestFilePath = inputs["android.manifest"][0].filePath;
- var args = ["package", "-f", "-m", "--no-crunch",
- "-M", manifestFilePath,
- "-I", ModUtils.moduleProperty(product, "androidJarFilePath"),
- "-F", outputs["android.ap_"][0].filePath, "--generate-dependencies"];
- var resources = inputs["android.resources"];
- if (resources && resources.length)
- args.push("-S", product.resourcesDir,
- "-J", ModUtils.moduleProperty(product, "generatedJavaFilesBaseDir"));
- if (product.moduleProperty("qbs", "buildVariant") === "debug")
- args.push("--debug-mode");
- if (File.exists(product.assetsDir))
- args.push("-A", product.assetsDir);
- var cmd = new Command(ModUtils.moduleProperty(product, "aaptFilePath"), args);
- cmd.description = "Processing resources";
- return [cmd];
- }
+ prepare: SdkUtils.prepareAaptGenerate.apply(SdkUtils, arguments)
}
Rule {
- inputs: ["android.manifest"] // FIXME: Workaround for the fact that rules need inputs
+ multiplex: true
+ condition: !!product.packageName
+
Artifact {
filePath: FileInfo.joinPaths(ModUtils.moduleProperty(product, "generatedJavaFilesDir"),
"BuildConfig.java")
@@ -190,18 +212,12 @@ Module {
Rule {
multiplex: true
inputs: ["java.class"]
+ inputsFromDependencies: ["java.jar"]
Artifact {
- filePath: "classes.dex"
+ filePath: FileInfo.joinPaths(product.Android.sdk.apkContentsDir, "classes.dex")
fileTags: ["android.dex"]
}
- prepare: {
- var dxFilePath = ModUtils.moduleProperty(product, "dxFilePath");
- var args = ["--dex", "--output", output.filePath,
- product.moduleProperty("java", "classFilesDir")];
- var cmd = new Command(dxFilePath, args);
- cmd.description = "Creating " + output.fileName;
- return [cmd];
- }
+ prepare: SdkUtils.prepareDex.apply(SdkUtils, arguments)
}
Rule {
@@ -215,7 +231,7 @@ Module {
if (inputs["android.nativelibrary"]) {
for (var i = 0; i < inputs["android.nativelibrary"].length; ++i) {
var inp = inputs["android.nativelibrary"][i];
- var destDir = FileInfo.joinPaths("lib",
+ var destDir = FileInfo.joinPaths(product.Android.sdk.apkContentsDir, "lib",
inp.moduleProperty("Android.ndk", "abi"));
libArtifacts.push({
filePath: FileInfo.joinPaths(destDir, inp.fileName),
@@ -260,47 +276,17 @@ Module {
}
}
- // TODO: ApkBuilderMain is deprecated. Do we have to provide our own tool directly
- // accessing com.android.sdklib.build.ApkBuilder or is there a simpler way?
Rule {
multiplex: true
inputs: [
- "android.dex", "android.ap_", "android.gdbserver", "android.stl",
- "android.nativelibrary-deployed"
+ "android.resources", "android.assets", "android.manifest",
+ "android.dex", "android.gdbserver", "android.stl",
+ "android.nativelibrary-deployed", "android.keystore"
]
Artifact {
- filePath: product.targetName + ".apk.unaligned"
- fileTags: ["android.apk.unaligned"]
- }
- prepare: {
- var args = ["-classpath", FileInfo.joinPaths(ModUtils.moduleProperty(product, "sdkDir"),
- "tools/lib/*"),
- "com.android.sdklib.build.ApkBuilderMain", output.filePath,
- "-z", inputs["android.ap_"][0].filePath,
- "-f", inputs["android.dex"][0].filePath];
- if (product.moduleProperty("qbs", "buildVariant") === "debug")
- args.push("-d");
- if (inputs["android.nativelibrary-deployed"])
- args.push("-nf", FileInfo.joinPaths(product.buildDirectory, "lib"));
- var cmd = new Command(product.moduleProperty("java", "interpreterFilePath"), args);
- cmd.description = "Generating " + output.fileName;
- return [cmd];
- }
- }
-
- Rule {
- multiplex: true
- inputs: ["android.apk.unaligned"]
- Artifact {
filePath: product.targetName + ".apk"
fileTags: ["android.apk"]
}
- prepare: {
- var zipalign = ModUtils.moduleProperty(product, "zipalignFilePath");
- var args = ["-f", "4", inputs["android.apk.unaligned"][0].filePath, output.filePath];
- var cmd = new Command(zipalign, args);
- cmd.description = "Creating " + output.fileName;
- return [cmd];
- }
+ prepare: SdkUtils.prepareAaptPackage.apply(SdkUtils, arguments)
}
}
diff --git a/share/qbs/modules/Android/sdk/utils.js b/share/qbs/modules/Android/sdk/utils.js
index 2f7701f6a..6d9fff65c 100644
--- a/share/qbs/modules/Android/sdk/utils.js
+++ b/share/qbs/modules/Android/sdk/utils.js
@@ -30,6 +30,7 @@
var File = require("qbs.File");
var FileInfo = require("qbs.FileInfo");
+var Process = require("qbs.Process");
var TextFile = require("qbs.TextFile");
var Utilities = require("qbs.Utilities");
@@ -41,7 +42,8 @@ function sourceAndTargetFilePathsFromInfoFiles(inputs, product, inputTag)
for (var i = 0; i < inputsLength; ++i) {
var infoFile = new TextFile(inputs[inputTag][i].filePath, TextFile.ReadOnly);
var sourceFilePath = infoFile.readLine();
- var targetFilePath = FileInfo.joinPaths(product.buildDirectory, infoFile.readLine());
+ var targetFilePath = FileInfo.joinPaths(product.Android.sdk.apkContentsDir,
+ infoFile.readLine());
if (!targetFilePaths.contains(targetFilePath)) {
sourceFilePaths.push(sourceFilePath);
targetFilePaths.push(targetFilePath);
@@ -97,3 +99,114 @@ function availableBuildToolsVersions(sdkDir) {
return versions;
}
+
+function prepareDex(project, product, inputs, outputs, input, output, explicitlyDependsOn) {
+ var dxFilePath = product.Android.sdk.dxFilePath;
+ var args = ["--dex", "--output", output.filePath, product.java.classFilesDir];
+
+ var jarFiles = [];
+ function traverseJarDeps(dep) {
+ if (dep.parameters.Android && dep.parameters.Android.sdk
+ && dep.parameters.Android.sdk.embedJar === false)
+ return;
+
+ var isJar = typeof dep.artifacts["java.jar"] !== "undefined";
+ if (!isJar)
+ return;
+
+ dep.artifacts["java.jar"].forEach(function(artifact) {
+ if (!jarFiles.contains(artifact.filePath))
+ jarFiles.push(artifact.filePath);
+ });
+ dep.dependencies.forEach(traverseJarDeps);
+ }
+ product.dependencies.forEach(traverseJarDeps);
+
+ args = args.concat(jarFiles);
+
+ var cmd = new Command(dxFilePath, args);
+ cmd.description = "Creating " + output.fileName;
+ return [cmd];
+}
+
+function commonAaptPackageArgs(project, product, inputs, outputs, input, output,
+ explicitlyDependsOn) {
+ var manifestFilePath = inputs["android.manifest"][0].filePath;
+ var args = ["package", "-f",
+ "-M", manifestFilePath,
+ "-I", product.Android.sdk.androidJarFilePath];
+ var resources = inputs["android.resources"];
+ if (resources && resources.length)
+ args.push("-S", product.resourcesDir);
+ if (product.qbs.buildVariant === "debug")
+ args.push("--debug-mode");
+ if (File.exists(product.assetsDir))
+ args.push("-A", product.assetsDir);
+ return args;
+}
+
+function prepareAaptGenerate(project, product, inputs, outputs, input, output,
+ explicitlyDependsOn) {
+ var args = commonAaptPackageArgs.apply(this, arguments);
+ args.push("--no-crunch", "-m");
+ var resources = inputs["android.resources"];
+ if (resources && resources.length)
+ args.push("-J", ModUtils.moduleProperty(product, "generatedJavaFilesBaseDir"));
+ var cmd = new Command(product.Android.sdk.aaptFilePath, args);
+ cmd.description = "Processing resources";
+ return [cmd];
+}
+
+function prepareAaptPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) {
+ var cmds = [];
+ var apkOutput = outputs["android.apk"][0];
+ var args = commonAaptPackageArgs.apply(this, arguments);
+ args.push("-F", apkOutput.filePath + ".unaligned");
+ args.push(product.Android.sdk.apkContentsDir);
+ var cmd = new Command(product.Android.sdk.aaptFilePath, args);
+ cmd.description = "Generating " + apkOutput.filePath;
+ cmds.push(cmd);
+
+ if (!product.Android.sdk.useApksigner) {
+ args = ["-sigalg", "SHA1withRSA", "-digestalg", "SHA1",
+ "-keystore", inputs["android.keystore"][0].filePath,
+ "-storepass", "android",
+ apkOutput.filePath + ".unaligned",
+ "androiddebugkey"];
+ cmd = new Command(product.java.jarsignerFilePath, args);
+ cmd.description = "Signing " + apkOutput.fileName;
+ cmds.push(cmd);
+ }
+
+ cmd = new Command(product.Android.sdk.zipalignFilePath,
+ ["-f", "4", apkOutput.filePath + ".unaligned", apkOutput.filePath]);
+ cmd.silent = true;
+ cmds.push(cmd);
+
+ cmd = new JavaScriptCommand();
+ cmd.silent = true;
+ cmd.unalignedApk = apkOutput.filePath + ".unaligned";
+ cmd.sourceCode = function() { File.remove(unalignedApk); };
+ cmds.push(cmd);
+
+ if (product.Android.sdk.useApksigner) {
+ // TODO: Implement full signing support, not just using the debug keystore
+ args = ["sign",
+ "--ks", inputs["android.keystore"][0].filePath,
+ "--ks-pass", "pass:android",
+ apkOutput.filePath];
+ cmd = new Command(product.Android.sdk.apksignerFilePath, args);
+ cmd.description = "Signing " + apkOutput.fileName;
+ cmds.push(cmd);
+ }
+
+ return cmds;
+}
+
+function createDebugKeyStoreCommandString(keytoolFilePath, keystoreFilePath) {
+ var args = ["-genkey", "-keystore", keystoreFilePath, "-alias", "androiddebugkey",
+ "-storepass", "android", "-keypass", "android", "-keyalg", "RSA",
+ "-keysize", "2048", "-validity", "10000", "-dname",
+ "CN=Android Debug,O=Android,C=US"];
+ return Process.shellQuote(keytoolFilePath, args);
+}
diff --git a/share/qbs/modules/cpp/CppModule.qbs b/share/qbs/modules/cpp/CppModule.qbs
index 1bb3d31c3..c340519ac 100644
--- a/share/qbs/modules/cpp/CppModule.qbs
+++ b/share/qbs/modules/cpp/CppModule.qbs
@@ -35,6 +35,9 @@ import qbs.WindowsUtils
Module {
condition: false
+
+ Depends { name: "cpufeatures" }
+
version: compilerVersion
property string compilerVersion:
[compilerVersionMajor, compilerVersionMinor, compilerVersionPatch].join(".")
@@ -62,13 +65,20 @@ Module {
property stringList defines
property stringList platformDefines: qbs.enableDebugCode ? [] : ["NDEBUG"]
- property stringList compilerDefines
+ property stringList compilerDefines: compilerDefinesByLanguage
+ ? ModUtils.flattenDictionary(compilerDefinesByLanguage["c"])
+ : []
+ property var compilerDefinesByLanguage: undefined
PropertyOptions {
- name: "compilerDefines"
+ name: "compilerDefinesByLanguage"
description: "preprocessor macros that are defined when using this particular compiler"
}
+ property stringList enableCompilerDefinesByLanguage: []
property string windowsApiCharacterSet
+ property string windowsApiFamily
+ property stringList windowsApiAdditionalPartitions
+ property bool requireAppContainer
property string minimumWindowsVersion
PropertyOptions {
@@ -176,6 +186,7 @@ Module {
property bool useRPaths: true
property bool useRPathLink
property string rpathLinkFlag
+ property bool discardUnusedData
property stringList assemblerFlags
PropertyOptions {
@@ -315,7 +326,7 @@ Module {
description: "whether to require app-extension-safe APIs only"
}
- property bool allowUnresolvedSymbols: false
+ property bool allowUnresolvedSymbols
property bool combineCSources: false
property bool combineCxxSources: false
@@ -431,6 +442,7 @@ Module {
}, "'" + architecture + "' is invalid. You must use the canonical name '" +
Utilities.canonicalArchitecture(architecture) + "'");
validator.setRequiredProperty("endianness", endianness);
+ validator.setRequiredProperty("compilerDefinesByLanguage", compilerDefinesByLanguage);
validator.setRequiredProperty("compilerVersion", compilerVersion);
validator.setRequiredProperty("compilerVersionMajor", compilerVersionMajor);
validator.setRequiredProperty("compilerVersionMinor", compilerVersionMinor);
diff --git a/share/qbs/modules/cpp/DarwinGCC.qbs b/share/qbs/modules/cpp/DarwinGCC.qbs
index 015fb71f5..33b5093ab 100644
--- a/share/qbs/modules/cpp/DarwinGCC.qbs
+++ b/share/qbs/modules/cpp/DarwinGCC.qbs
@@ -69,16 +69,17 @@ UnixGCC {
targetAbi: "macho"
imageFormat: "macho"
- compilerDefines: ["__GNUC__=4", "__APPLE__"]
cxxStandardLibrary: libcxxAvailable ? "libc++" : base
loadableModulePrefix: ""
loadableModuleSuffix: ".bundle"
dynamicLibrarySuffix: ".dylib"
- variantSuffix: {
- // "release" corresponds to the "normal" (non-suffixed) variant
- if (qbs.buildVariant !== "release")
- return "_" + qbs.buildVariant;
- return "";
+
+ Properties {
+ condition: product.multiplexByQbsProperties.contains("buildVariants")
+ && qbs.buildVariants && qbs.buildVariants.length > 1
+ && (!product.aggregate || !!product.multiplexConfigurationId)
+ && qbs.buildVariant !== "release"
+ variantSuffix: "_" + qbs.buildVariant
}
separateDebugInformation: true
diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs
index 8a83f9d5e..638882aa7 100644
--- a/share/qbs/modules/cpp/GenericGCC.qbs
+++ b/share/qbs/modules/cpp/GenericGCC.qbs
@@ -60,7 +60,8 @@ CppModule {
Probes.GccProbe {
id: gccProbe
- compilerFilePath: compilerPath
+ compilerFilePathByLanguage: compilerPathByLanguage
+ enableDefinesByLanguage: enableCompilerDefinesByLanguage
environment: buildEnv
flags: targetDriverFlags.concat(sysrootFlags)
_sysroot: sysroot
@@ -98,6 +99,8 @@ CppModule {
qbs.architecture: gccProbe.found ? gccProbe.architecture : original
endianness: gccProbe.endianness
+ compilerDefinesByLanguage: gccProbe.compilerDefinesByLanguage
+
compilerVersionMajor: gccVersionProbe.versionMajor
compilerVersionMinor: gccVersionProbe.versionMinor
compilerVersionPatch: gccVersionProbe.versionPatch
diff --git a/share/qbs/modules/cpp/darwin.js b/share/qbs/modules/cpp/darwin.js
index 3889de8e1..484076bca 100644
--- a/share/qbs/modules/cpp/darwin.js
+++ b/share/qbs/modules/cpp/darwin.js
@@ -51,7 +51,8 @@ function lipoOutputArtifacts(product, inputs, fileTag, debugSuffix) {
filePath: product.destinationDirectory + "/.sosymbols/"
+ PathTools.dynamicLibraryFilePath(product, variant.suffix),
fileTags: ["dynamiclibrary_copy"],
- qbs: { buildVariant: variant.name, variantSuffix: variant.suffix },
+ qbs: { buildVariant: variant.name },
+ cpp: { variantSuffix: variant.suffix },
alwaysUpdated: false
};
}));
@@ -70,7 +71,9 @@ function lipoOutputArtifacts(product, inputs, fileTag, debugSuffix) {
// Finder/LaunchServices can launch it normally but for simplicity we'll just use the symlink
// approach for all bundle types.
var defaultVariant;
- if (!buildVariants.some(function (x) { return x.name === "release"; })) {
+ if (!buildVariants.some(function (x) { return x.name === "release"; })
+ && product.multiplexByQbsProperties.contains("buildVariants")
+ && product.qbs.buildVariants && product.qbs.buildVariants.length > 1) {
var defaultBuildVariant = product.qbs.defaultBuildVariant;
buildVariants.map(function (variant) {
if (variant.name === defaultBuildVariant)
@@ -103,7 +106,6 @@ function lipoOutputArtifacts(product, inputs, fileTag, debugSuffix) {
fileTags: tags,
qbs: {
buildVariant: variant.name,
- variantSuffix: variant.suffix,
_buildVariantFileName: variant.isSymLink && defaultVariant
? FileInfo.fileName(PathTools.linkerOutputFilePath(
fileTag, product,
@@ -113,6 +115,9 @@ function lipoOutputArtifacts(product, inputs, fileTag, debugSuffix) {
bundle: {
_bundleFilePath: product.destinationDirectory + "/"
+ PathTools.bundleExecutableFilePath(product, variant.suffix)
+ },
+ cpp: {
+ variantSuffix: variant.suffix
}
};
}));
diff --git a/share/qbs/modules/cpp/gcc.js b/share/qbs/modules/cpp/gcc.js
index 4a57e83cb..d003d8d89 100644
--- a/share/qbs/modules/cpp/gcc.js
+++ b/share/qbs/modules/cpp/gcc.js
@@ -241,6 +241,8 @@ function linkerFlags(project, product, inputs, output, linkerPath) {
var systemRunPaths = product.cpp.systemRunPaths || [];
var i, args = additionalCompilerAndLinkerFlags(product);
+ var escapableLinkerFlags = [];
+
if (output.fileTags.contains("dynamiclibrary")) {
if (isDarwin) {
args.push((function () {
@@ -259,14 +261,9 @@ function linkerFlags(project, product, inputs, output, linkerPath) {
var internalVersion = product.cpp.internalVersion;
if (internalVersion && isNumericProductVersion(internalVersion))
args.push("-current_version", internalVersion);
-
- args = args.concat(escapeLinkerFlags(product, inputs, [
- "-install_name",
- UnixUtils.soname(product, output.fileName)]));
+ escapableLinkerFlags.push("-install_name", UnixUtils.soname(product, output.fileName));
} else {
- args = args.concat(escapeLinkerFlags(product, inputs, [
- "-soname=" +
- UnixUtils.soname(product, output.fileName)]));
+ escapableLinkerFlags.push("-soname=" + UnixUtils.soname(product, output.fileName));
}
}
@@ -275,11 +272,9 @@ function linkerFlags(project, product, inputs, output, linkerPath) {
if (output.fileTags.containsAny(["dynamiclibrary", "loadablemodule"])) {
if (isDarwin)
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["-headerpad_max_install_names"]));
+ escapableLinkerFlags.push("-headerpad_max_install_names");
else
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["--as-needed"]));
+ escapableLinkerFlags.push("--as-needed");
}
if (isLegacyQnxSdk(product)) {
@@ -291,45 +286,37 @@ function linkerFlags(project, product, inputs, output, linkerPath) {
var targetLinkerFlags = product.cpp.targetLinkerFlags;
if (targetLinkerFlags)
- args = args.concat(escapeLinkerFlags(product, inputs, targetLinkerFlags));
+ Array.prototype.push.apply(escapableLinkerFlags, targetLinkerFlags);
var sysroot = product.cpp.syslibroot;
if (sysroot) {
if (product.qbs.toolchain.contains("qcc"))
- args = args.concat(escapeLinkerFlags(product, inputs, ["--sysroot=" + sysroot]));
+ escapableLinkerFlags.push("--sysroot=" + sysroot);
else if (isDarwin)
- args = args.concat(escapeLinkerFlags(product, inputs, ["-syslibroot", sysroot]));
+ escapableLinkerFlags.push("-syslibroot", sysroot);
else
args.push("--sysroot=" + sysroot); // do not escape, compiler-as-linker also needs it
}
- if (isDarwin) {
- var unresolvedSymbolsAction;
- unresolvedSymbolsAction = product.cpp.allowUnresolvedSymbols
- ? "suppress" : "error";
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["-undefined", unresolvedSymbolsAction]));
- } else if (product.cpp.allowUnresolvedSymbols) {
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["--unresolved-symbols=ignore-all"]));
+ if (product.cpp.allowUnresolvedSymbols) {
+ if (isDarwin)
+ escapableLinkerFlags.push("-undefined", "suppress");
+ else
+ escapableLinkerFlags.push("--unresolved-symbols=ignore-all");
}
for (i in rpaths) {
if (systemRunPaths.indexOf(rpaths[i]) === -1)
- args = args.concat(escapeLinkerFlags(product, inputs, ["-rpath", rpaths[i]]));
+ escapableLinkerFlags.push("-rpath", rpaths[i]);
}
if (product.cpp.entryPoint)
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["-e", product.cpp.entryPoint]));
+ escapableLinkerFlags.push("-e", product.cpp.entryPoint);
if (product.qbs.toolchain.contains("mingw")) {
if (product.consoleApplication !== undefined)
- args = args.concat(escapeLinkerFlags(product, inputs, [
- "-subsystem",
- product.consoleApplication
- ? "console"
- : "windows"]));
+ escapableLinkerFlags.push("-subsystem",
+ product.consoleApplication ? "console" : "windows");
var minimumWindowsVersion = product.cpp.minimumWindowsVersion;
if (minimumWindowsVersion) {
@@ -339,14 +326,10 @@ function linkerFlags(project, product, inputs, output, linkerPath) {
var minor = subsystemVersion.split('.')[1];
// http://sourceware.org/binutils/docs/ld/Options.html
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["--major-subsystem-version", major]));
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["--minor-subsystem-version", minor]));
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["--major-os-version", major]));
- args = args.concat(escapeLinkerFlags(product, inputs,
- ["--minor-os-version", minor]));
+ escapableLinkerFlags.push("--major-subsystem-version", major,
+ "--minor-subsystem-version", minor,
+ "--major-os-version", major,
+ "--minor-os-version", minor);
}
}
}
@@ -371,21 +354,20 @@ function linkerFlags(project, product, inputs, output, linkerPath) {
var linkerScripts = inputs.linkerscript
? inputs.linkerscript.map(function(a) { return a.filePath; }) : [];
- args = args.concat(escapeLinkerFlags(product, inputs, [].uniqueConcat(linkerScripts)
- .map(function(path) { return '-T' + path })));
+ Array.prototype.push.apply(escapableLinkerFlags, [].uniqueConcat(linkerScripts)
+ .map(function(path) { return '-T' + path }));
var versionScripts = inputs.versionscript
? inputs.versionscript.map(function(a) { return a.filePath; }) : [];
- args = args.concat(escapeLinkerFlags(product, inputs, [].uniqueConcat(versionScripts)
- .map(function(path) { return '--version-script=' + path })));
+ Array.prototype.push.apply(escapableLinkerFlags, [].uniqueConcat(versionScripts)
+ .map(function(path) { return '--version-script=' + path }));
if (isDarwin && product.cpp.warningLevel === "none")
args.push('-w');
args = args.concat(configFlags(product, useCompilerDriverLinker(product, inputs)));
- args = args.concat(escapeLinkerFlags(
- product, inputs, product.cpp.platformLinkerFlags));
- args = args.concat(escapeLinkerFlags(product, inputs, product.cpp.linkerFlags));
+ Array.prototype.push.apply(escapableLinkerFlags, product.cpp.platformLinkerFlags);
+ Array.prototype.push.apply(escapableLinkerFlags, product.cpp.linkerFlags);
// Note: due to the QCC response files hack in prepareLinker(), at least one object file or
// library file must follow the output file path so that QCC has something to process before
@@ -437,15 +419,12 @@ function linkerFlags(project, product, inputs, output, linkerPath) {
if (!["lazy", "reexport", "upward", "weak"].contains(symbolLinkMode))
throw new Error("unknown value '" + symbolLinkMode + "' for cpp.symbolLinkMode");
- var flags;
if (FileInfo.isAbsolutePath(lib) || lib.startsWith('@'))
- flags = ["-" + symbolLinkMode + "_library", lib];
+ escapableLinkerFlags.push("-" + symbolLinkMode + "_library", lib);
else if (dep.framework)
- flags = ["-" + symbolLinkMode + "_framework", lib];
+ escapableLinkerFlags.push("-" + symbolLinkMode + "_framework", lib);
else
- flags = ["-" + symbolLinkMode + "-l" + lib];
-
- Array.prototype.push.apply(args, escapeLinkerFlags(product, inputs, flags));
+ escapableLinkerFlags.push("-" + symbolLinkMode + "-l" + lib);
} else if (FileInfo.isAbsolutePath(lib) || lib.startsWith('@')) {
args.push(dep.framework ? PathTools.frameworkExecutablePath(lib) : lib);
} else if (dep.framework) {
@@ -458,19 +437,31 @@ function linkerFlags(project, product, inputs, output, linkerPath) {
Array.prototype.push.apply(args,
escapeLinkerFlags(product, inputs, ["--no-whole-archive"]));
}
+ var discardUnusedData = product.cpp.discardUnusedData;
+ if (discardUnusedData !== undefined) {
+ var flags = [];
+ if (discardUnusedData === true) {
+ if (isDarwin)
+ escapableLinkerFlags.push("-dead_strip");
+ else
+ escapableLinkerFlags.push("--gc-sections");
+ } else if (!isDarwin) {
+ escapableLinkerFlags.push("--no-gc-sections");
+ }
+ }
if (product.cpp.useRPathLink) {
if (!product.cpp.rpathLinkFlag)
throw new Error("Using rpath-link but cpp.rpathLinkFlag is not defined");
- args = args.concat(escapeLinkerFlags(
- product, inputs,
- libraryDependencies.rpath_link.map(
- function(dir) {
- return product.cpp.rpathLinkFlag + dir;
- })));
+ Array.prototype.push.apply(escapableLinkerFlags, libraryDependencies.rpath_link.map(
+ function(dir) {
+ return product.cpp.rpathLinkFlag + dir;
+ }));
}
- return args;
+ var escapedLinkerFlags = escapeLinkerFlags(product, inputs, escapableLinkerFlags);
+ Array.prototype.push.apply(escapedLinkerFlags, args);
+ return escapedLinkerFlags;
}
// for compiler AND linker
@@ -569,6 +560,46 @@ function qnxLangArgs(config, tag) {
}
}
+function handleCpuFeatures(input, flags) {
+ function potentiallyAddFlagForFeature(propName, flagName) {
+ var propValue = input.cpufeatures[propName];
+ if (propValue === true)
+ flags.push("-m" + flagName);
+ else if (propValue === false)
+ flags.push("-mno-" + flagName);
+ }
+
+ if (!input.qbs.architecture)
+ return;
+ if (input.qbs.architecture.startsWith("x86")) {
+ potentiallyAddFlagForFeature("x86_avx", "avx");
+ potentiallyAddFlagForFeature("x86_avx2", "avx2");
+ potentiallyAddFlagForFeature("x86_avx512bw", "avx512bw");
+ potentiallyAddFlagForFeature("x86_avx512cd", "avx512cd");
+ potentiallyAddFlagForFeature("x86_avx512dq", "avx512dq");
+ potentiallyAddFlagForFeature("x86_avx512er", "avx512er");
+ potentiallyAddFlagForFeature("x86_avx512f", "avx512f");
+ potentiallyAddFlagForFeature("x86_avx512ifma", "avx512ifma");
+ potentiallyAddFlagForFeature("x86_avx512pf", "avx512pf");
+ potentiallyAddFlagForFeature("x86_avx512vbmi", "avx512vbmi");
+ potentiallyAddFlagForFeature("x86_avx512vl", "avx512vl");
+ potentiallyAddFlagForFeature("x86_f16c", "f16c");
+ potentiallyAddFlagForFeature("x86_sse2", "sse2");
+ potentiallyAddFlagForFeature("x86_sse3", "sse3");
+ potentiallyAddFlagForFeature("x86_sse4_1", "sse4.1");
+ potentiallyAddFlagForFeature("x86_sse4_2", "sse4.2");
+ potentiallyAddFlagForFeature("x86_ssse3", "ssse3");
+ } else if (input.qbs.architecture.startsWith("arm")) {
+ if (input.cpufeatures.arm_neon === true)
+ flags.push("-mfpu=neon");
+ if (input.cpufeatures.arm_vfpv4 === true)
+ flags.push("-mfpu=vfpv4");
+ } else if (input.qbs.architecture.startsWith("mips")) {
+ potentiallyAddFlagForFeature("mips_dsp", "dsp");
+ potentiallyAddFlagForFeature("mips_dspr2", "dspr2");
+ }
+}
+
function compilerFlags(project, product, input, output, explicitlyDependsOn) {
var i;
@@ -590,6 +621,7 @@ function compilerFlags(project, product, input, output, explicitlyDependsOn) {
var args = additionalCompilerAndLinkerFlags(product);
Array.prototype.push.apply(args, product.cpp.sysrootFlags);
+ handleCpuFeatures(input, args);
if (input.cpp.debugInformation)
args.push('-g');
@@ -715,9 +747,7 @@ function compilerFlags(project, product, input, output, explicitlyDependsOn) {
allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(systemIncludePaths);
if (distributionIncludePaths)
allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(distributionIncludePaths);
- args = args.concat(allSystemIncludePaths.map(function(path) {
- return input.cpp.systemIncludeFlag + path;
- }));
+ allSystemIncludePaths.forEach(function(v) { args.push(input.cpp.systemIncludeFlag, v); });
var minimumWindowsVersion = input.cpp.minimumWindowsVersion;
if (minimumWindowsVersion && product.qbs.targetOS.contains("windows")) {
@@ -1263,7 +1293,7 @@ function isNumericProductVersion(version) {
return version && version.match(/^([0-9]+\.){0,3}[0-9]+$/);
}
-function dumpMacros(env, compilerFilePath, args, nullDevice) {
+function dumpMacros(env, compilerFilePath, args, nullDevice, tag) {
var p = new Process();
try {
p.setEnv("LC_ALL", "C");
@@ -1271,7 +1301,8 @@ function dumpMacros(env, compilerFilePath, args, nullDevice) {
p.setEnv(key, env[key]);
// qcc NEEDS the explicit -Wp, prefix to -dM; clang and gcc do not but all three accept it
p.exec(compilerFilePath,
- (args || []).concat(["-Wp,-dM", "-E", "-x", "c", nullDevice]), true);
+ (args || []).concat(["-Wp,-dM", "-E", "-x", languageName(tag || "c") , nullDevice]),
+ true);
var map = {};
p.readStdOut().trim().split("\n").map(function (line) {
var parts = line.split(" ", 3);
diff --git a/share/qbs/modules/cpp/msvc.js b/share/qbs/modules/cpp/msvc.js
index 6ca6dad00..13e5d8821 100644
--- a/share/qbs/modules/cpp/msvc.js
+++ b/share/qbs/modules/cpp/msvc.js
@@ -34,13 +34,27 @@ var ModUtils = require("qbs.ModUtils");
var Utilities = require("qbs.Utilities");
var WindowsUtils = require("qbs.WindowsUtils");
-function compilerVersionDefine(cpp) {
- var result = '_MSC_VER=' + cpp.compilerVersionMajor;
- var s = cpp.compilerVersionMinor.toString();
- while (s.length < 2)
- s = '0' + s;
- result += s;
- return result;
+function handleCpuFeatures(input, flags) {
+ if (!input.qbs.architecture)
+ return;
+ if (input.qbs.architecture.startsWith("x86")) {
+ if (input.qbs.architecture === "x86") {
+ var sse2 = input.cpufeatures.x86_sse2;
+ if (sse2 === true)
+ flags.push("/arch:SSE2");
+ if (sse2 === false)
+ flags.push("/arch:IA32");
+ }
+ if (input.cpufeatures.x86_avx === true)
+ flags.push("/arch:AVX");
+ if (input.cpufeatures.x86_avx2 === true)
+ flags.push("/arch:AVX2");
+ } else if (input.qbs.architecture.startsWith("arm")) {
+ if (input.cpufeatures.arm_vfpv4 === true)
+ flags.push("/arch:VFPv4");
+ if (input.cpp.machineType === "armv7ve")
+ flags.push("/arch:ARMv7VE");
+ }
}
function addLanguageVersionFlag(input, args) {
@@ -67,6 +81,8 @@ function prepareCompiler(project, product, inputs, outputs, input, output, expli
var debugInformation = input.cpp.debugInformation;
var args = ['/nologo', '/c']
+ handleCpuFeatures(input, args);
+
// Determine which C-language we're compiling
var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(Object.keys(outputs)));
if (!["c", "cpp"].contains(tag))
@@ -285,6 +301,14 @@ function linkerSupportsWholeArchive(product)
return Utilities.versionCompare(product.cpp.compilerVersion, "19.0.25123") >= 0
}
+function handleDiscardProperty(product, flags) {
+ var discardUnusedData = product.cpp.discardUnusedData;
+ if (discardUnusedData === true)
+ flags.push("/OPT:REF");
+ else if (discardUnusedData === false)
+ flags.push("/OPT:NOREF");
+}
+
function prepareLinker(project, product, inputs, outputs, input, output) {
var i;
var linkDLL = (outputs.dynamiclibrary ? true : false)
@@ -330,6 +354,10 @@ function prepareLinker(project, product, inputs, outputs, input, output) {
break;
}
+ var requireAppContainer = product.cpp.requireAppContainer;
+ if (requireAppContainer !== undefined)
+ args.push("/APPCONTAINER" + (requireAppContainer ? "" : ":NO"));
+
var minimumWindowsVersion = product.cpp.minimumWindowsVersion;
var subsystemSwitch = undefined;
if (minimumWindowsVersion || product.consoleApplication !== undefined) {
@@ -402,6 +430,7 @@ function prepareLinker(project, product, inputs, outputs, input, output) {
for (i in libraryPaths) {
args.push('/LIBPATH:' + FileInfo.toWindowsSeparators(libraryPaths[i]))
}
+ handleDiscardProperty(product, args);
var linkerFlags = product.cpp.platformLinkerFlags.concat(product.cpp.linkerFlags);
args = args.concat(linkerFlags);
if (product.cpp.allowUnresolvedSymbols)
diff --git a/share/qbs/modules/cpp/windows-mingw.qbs b/share/qbs/modules/cpp/windows-mingw.qbs
index a7c468b6b..f09c4dd22 100644
--- a/share/qbs/modules/cpp/windows-mingw.qbs
+++ b/share/qbs/modules/cpp/windows-mingw.qbs
@@ -47,7 +47,15 @@ GenericGCC {
imageFormat: "pe"
windowsApiCharacterSet: "unicode"
platformDefines: base.concat(WindowsUtils.characterSetDefines(windowsApiCharacterSet))
- compilerDefines: ['__GNUC__', 'WIN32', '_WIN32']
+ .concat("WIN32")
+
+ Properties {
+ condition: product.multiplexByQbsProperties.contains("buildVariants")
+ && qbs.buildVariants && qbs.buildVariants.length > 1
+ && qbs.buildVariant !== "release"
+ && product.type.containsAny(["staticlibrary", "dynamiclibrary"])
+ variantSuffix: "d"
+ }
property string windresName: 'windres'
property path windresPath: { return toolchainPathPrefix + windresName }
diff --git a/share/qbs/modules/cpp/windows-msvc.qbs b/share/qbs/modules/cpp/windows-msvc.qbs
index 59348bd51..5736a071a 100644
--- a/share/qbs/modules/cpp/windows-msvc.qbs
+++ b/share/qbs/modules/cpp/windows-msvc.qbs
@@ -43,8 +43,6 @@ CppModule {
qbs.targetOS.contains('windows') &&
qbs.toolchain && qbs.toolchain.contains('msvc')
- id: module
-
Probes.BinaryProbe {
id: compilerPathProbe
condition: !toolchainInstallPath
@@ -54,6 +52,7 @@ CppModule {
Probes.MsvcProbe {
id: msvcProbe
compilerFilePath: compilerPath
+ enableDefinesByLanguage: enableCompilerDefinesByLanguage
preferredArchitecture: qbs.architecture
}
@@ -65,15 +64,24 @@ CppModule {
compilerIncludePaths: msvcProbe.includePaths
windowsApiCharacterSet: "unicode"
- platformDefines: base.concat(WindowsUtils.characterSetDefines(windowsApiCharacterSet))
- .concat("WIN32")
+ platformDefines: {
+ var defines = base.concat(WindowsUtils.characterSetDefines(windowsApiCharacterSet))
+ .concat("WIN32");
+ var def = WindowsUtils.winapiFamilyDefine(windowsApiFamily);
+ if (def)
+ defines.push("WINAPI_FAMILY=WINAPI_FAMILY_" + def);
+ (windowsApiAdditionalPartitions || []).map(function (name) {
+ defines.push("WINAPI_PARTITION_" + WindowsUtils.winapiPartitionDefine(name) + "=1");
+ });
+ return defines;
+ }
platformCommonCompilerFlags: {
var flags = base;
if (compilerVersionMajor >= 18) // 2013
flags.push("/FS");
return flags;
}
- compilerDefines: ['_WIN32', MSVC.compilerVersionDefine(module)]
+ compilerDefinesByLanguage: msvcProbe.compilerDefinesByLanguage
warningLevel: "default"
compilerName: "cl.exe"
compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName)
@@ -110,6 +118,13 @@ CppModule {
debugInfoSuffix: ".pdb"
property string dynamicLibraryImportSuffix: ".lib"
imageFormat: "pe"
+ Properties {
+ condition: product.multiplexByQbsProperties.contains("buildVariants")
+ && qbs.buildVariants && qbs.buildVariants.length > 1
+ && qbs.buildVariant !== "release"
+ && product.type.containsAny(["staticlibrary", "dynamiclibrary"])
+ variantSuffix: "d"
+ }
property var buildEnv: msvcProbe.buildEnv
diff --git a/share/qbs/modules/cpufeatures/cpufeatures.qbs b/share/qbs/modules/cpufeatures/cpufeatures.qbs
new file mode 100644
index 000000000..a7ef95193
--- /dev/null
+++ b/share/qbs/modules/cpufeatures/cpufeatures.qbs
@@ -0,0 +1,25 @@
+import qbs
+
+Module {
+ property bool arm_neon
+ property bool arm_vfpv4
+ property bool mips_dsp
+ property bool mips_dspr2
+ property bool x86_avx
+ property bool x86_avx2
+ property bool x86_avx512bw
+ property bool x86_avx512cd
+ property bool x86_avx512dq
+ property bool x86_avx512er
+ property bool x86_avx512f
+ property bool x86_avx512ifma
+ property bool x86_avx512pf
+ property bool x86_avx512vbmi
+ property bool x86_avx512vl
+ property bool x86_f16c
+ property bool x86_sse2
+ property bool x86_sse3
+ property bool x86_sse4_1
+ property bool x86_sse4_2
+ property bool x86_ssse3
+}
diff --git a/share/qbs/modules/java/JavaModule.qbs b/share/qbs/modules/java/JavaModule.qbs
index 7f0bb080c..dcf6c8ba2 100644
--- a/share/qbs/modules/java/JavaModule.qbs
+++ b/share/qbs/modules/java/JavaModule.qbs
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qbs.
@@ -43,6 +43,11 @@ Module {
environmentPaths: (jdkPath ? [jdkPath] : []).concat(base)
}
+ Probes.JdkVersionProbe {
+ id: jdkVersionProbe
+ javac: compilerFilePath
+ }
+
property stringList additionalClassPaths
property stringList additionalCompilerFlags
property stringList additionalJarFlags
@@ -54,11 +59,16 @@ Module {
property string interpreterName: "java"
property string jarFilePath: FileInfo.joinPaths(jdkPath, "bin", jarName)
property string jarName: "jar"
+ property string jarsignerFilePath: FileInfo.joinPaths(jdkPath, "bin", jarsignerName)
+ property string jarsignerName: "jarsigner"
+ property string keytoolFilePath: FileInfo.joinPaths(jdkPath, "bin", keytoolName)
+ property string keytoolName: "keytool"
- property path jdkPath: jdk.path
+ property string jdkPath: jdk.path
version: compilerVersion
- property string compilerVersion: jdk.version ? jdk.version[1] : undefined
+ property string compilerVersion: jdkVersionProbe.version
+ ? jdkVersionProbe.version[1] : undefined
property var compilerVersionParts: compilerVersion ? compilerVersion.split(/[\._]/).map(function(item) { return parseInt(item, 10); }) : []
property int compilerVersionMajor: compilerVersionParts[0]
property int compilerVersionMinor: compilerVersionParts[1]
@@ -89,7 +99,6 @@ Module {
description: "properties to add to the manifest file when building a JAR"
}
- property path manifestFile
PropertyOptions {
name: "manifestFile"
description: "Use files tagged \"java.manifest\" instead."
diff --git a/share/qbs/modules/vcs/vcs-module.qbs b/share/qbs/modules/vcs/vcs-module.qbs
new file mode 100644
index 000000000..290547713
--- /dev/null
+++ b/share/qbs/modules/vcs/vcs-module.qbs
@@ -0,0 +1,153 @@
+import qbs
+import qbs.File
+import qbs.FileInfo
+import qbs.Process
+import qbs.TextFile
+import qbs.Utilities
+
+Module {
+ property string type: typeProbe.type
+ property string repoDir: project.sourceDirectory
+ property string toolFilePath: {
+ if (type === "git")
+ return "git";
+ if (type === "svn")
+ return "svn";
+ }
+
+ property string headerFileName: "vcs-repo-state.h"
+ readonly property string repoState: gitProbe.repoState || subversionProbe.repoState
+
+ // Internal
+ readonly property string includeDir: FileInfo.joinPaths(product.buildDirectory, "vcs-include")
+ readonly property string metaDataBaseDir: typeProbe.metaDataBaseDir
+
+ PropertyOptions {
+ name: "type"
+ allowedValues: ["git", "svn"]
+ description: "the version control system your project is using"
+ }
+
+ Depends { name: "cpp"; condition: headerFileName }
+ Properties {
+ condition: headerFileName
+ cpp.includePaths: [includeDir]
+ }
+
+ Probe {
+ id: typeProbe
+
+ property string tool: toolFilePath
+ property string theRepoDir: repoDir
+
+ property string type
+ property string metaDataBaseDir
+
+ configure: {
+ var detector = new Process();
+ try {
+ detector.setWorkingDirectory(theRepoDir);
+ if (detector.exec(tool || "git", ["rev-parse", "--git-dir"]) === 0) {
+ found = true;
+ type = "git";
+ metaDataBaseDir = detector.readStdOut().trim();
+ return;
+ }
+ if (detector.exec(tool || "svn",
+ ["info", "--show-item", "wc-root", "--no-newline"]) === 0) {
+ found = true
+ type = "svn";
+ metaDataBaseDir = FileInfo.joinPaths(detector.readStdOut(), ".svn");
+ return;
+ } else if (detector.exec(tool || "svn", ["info"]) === 0) {
+ if (detector.exec(tool || "svn", ["--version", "--quiet"]) === 0
+ && Utilities.versionCompare(detector.readStdOut().trim(), "1.9") < 0) {
+ throw "svn too old, version >= 1.9 required";
+ }
+ }
+ } finally {
+ detector.close();
+ }
+ }
+ }
+
+ Probe {
+ id: gitProbe
+ condition: type === "git"
+
+ property string tool: toolFilePath
+ property string theRepoDir: repoDir
+ property string filePath: FileInfo.joinPaths(metaDataBaseDir, "logs/HEAD")
+ property var timestamp: File.lastModified(filePath)
+
+ property string repoState
+
+ configure: {
+ if (!File.exists(filePath))
+ return; // No commits yet.
+ var proc = new Process();
+ try {
+ proc.setWorkingDirectory(theRepoDir);
+ proc.exec(tool, ["describe", "--always", "HEAD"], true);
+ repoState = proc.readStdOut().trim();
+ if (repoState)
+ found = true;
+ } finally {
+ proc.close();
+ }
+ }
+ }
+
+ Probe {
+ id: subversionProbe
+ condition: type === "svn"
+
+ property string tool: toolFilePath
+ property string theRepoDir: repoDir
+ property string filePath: FileInfo.joinPaths(metaDataBaseDir, "wc.db")
+ property var timestamp: File.lastModified(filePath)
+
+ property string repoState
+
+ configure: {
+ var proc = new Process();
+ try {
+ proc.setWorkingDirectory(theRepoDir);
+ proc.exec(tool, ["info", "-r", "HEAD", "--show-item", "revision", "--no-newline"],
+ true);
+ repoState = proc.readStdOut();
+ if (repoState)
+ found = true;
+ } finally {
+ proc.close();
+ }
+ }
+ }
+
+ Rule {
+ condition: repoState && headerFileName
+ multiplex: true
+ Artifact {
+ filePath: FileInfo.joinPaths(product.vcs.includeDir, product.vcs.headerFileName)
+ fileTags: ["hpp"]
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "generating " + output.fileName;
+ cmd.highlight = "codegen";
+ cmd.repoState = product.vcs.repoState;
+ cmd.sourceCode = function() {
+ var f = new TextFile(output.filePath, TextFile.WriteOnly);
+ try {
+ f.writeLine("#ifndef VCS_REPO_STATE_H");
+ f.writeLine("#define VCS_REPO_STATE_H");
+ f.writeLine('#define VCS_REPO_STATE "' + repoState + '"')
+ f.writeLine("#endif");
+ } finally {
+ f.close();
+ }
+ };
+ return [cmd];
+ }
+ }
+}
diff --git a/src/3rdparty/python/update.sh b/src/3rdparty/python/update.sh
deleted file mode 100755
index 6d6ade63f..000000000
--- a/src/3rdparty/python/update.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-repos=(biplist.git@v1.0.2 dmgbuild.git@v1.3.1 ds_store@v1.1.2 mac_alias.git@v2.0.6)
-for repo in "${repos[@]}" ; do
- pip install -U --isolated "--prefix=$PWD" --no-binary :all: --no-compile --no-deps \
- "git+git://github.com/qbs/$repo"
-done
-rm lib/python2.7/site-packages/dmgbuild/resources/*.tiff
diff --git a/src/app/qbs-setup-qt/qbs-setup-qt.qbs b/src/app/qbs-setup-qt/qbs-setup-qt.qbs
index 033f046a5..8bd4ae263 100644
--- a/src/app/qbs-setup-qt/qbs-setup-qt.qbs
+++ b/src/app/qbs-setup-qt/qbs-setup-qt.qbs
@@ -13,7 +13,7 @@ QbsApp {
Group {
name: "MinGW specific files"
condition: qbs.toolchain.contains("mingw")
- files: ["qbs-setup-qt.rc"]
+ files: ["qbs-setup-qt.exe.manifest", "qbs-setup-qt.rc"]
}
}
diff --git a/src/app/qbs-setup-qt/setupqt.cpp b/src/app/qbs-setup-qt/setupqt.cpp
index 55db5415b..d6eb282eb 100644
--- a/src/app/qbs-setup-qt/setupqt.cpp
+++ b/src/app/qbs-setup-qt/setupqt.cpp
@@ -377,9 +377,6 @@ static QStringList qbsTargetOsFromQtMkspec(const QString &mkspec)
if (mkspec.startsWith(QLatin1String("android-")))
return QStringList() << QLatin1String("android") << QLatin1String("linux")
<< QLatin1String("unix");
- if (mkspec.startsWith(QLatin1String("blackberry")))
- return QStringList() << QLatin1String("blackberry") << QLatin1String("qnx")
- << QLatin1String("unix");
if (mkspec.startsWith(QLatin1String("darwin-")))
return QStringList() << QLatin1String("darwin") << QLatin1String("bsd")
<< QLatin1String("unix");
@@ -423,14 +420,8 @@ static QStringList qbsTargetOsFromQtMkspec(const QString &mkspec)
return QStringList() << QLatin1String("unixware") << QLatin1String("unix");
if (mkspec.startsWith(QLatin1String("vxworks-")))
return QStringList() << QLatin1String("vxworks") << QLatin1String("unix");
- if (mkspec.startsWith(QLatin1String("win32-")))
+ if (mkspec.startsWith(QLatin1String("win32-")) || mkspec.startsWith(QLatin1String("winrt-")))
return QStringList() << QLatin1String("windows");
- if (mkspec.startsWith(QLatin1String("wince")))
- return QStringList() << QLatin1String("windowsce") << QLatin1String("windows");
- if (mkspec.startsWith(QLatin1String("winphone-")))
- return QStringList() << QLatin1String("windowsphone") << QLatin1String("winrt") << QLatin1String("windows");
- if (mkspec.startsWith(QLatin1String("winrt-")))
- return QStringList() << QLatin1String("winrt") << QLatin1String("windows");
return QStringList();
}
diff --git a/src/app/qbs-setup-toolchains/probe.cpp b/src/app/qbs-setup-toolchains/probe.cpp
index 735285718..2bb99abfd 100644
--- a/src/app/qbs-setup-toolchains/probe.cpp
+++ b/src/app/qbs-setup-toolchains/probe.cpp
@@ -48,6 +48,7 @@
#include <tools/profile.h>
#include <tools/settings.h>
#include <tools/toolchains.h>
+#include <tools/stlutils.h>
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
@@ -185,6 +186,17 @@ private:
QString m_toolchainPrefix;
};
+static bool doesProfileTargetOS(const Profile &profile, const QString &os)
+{
+ const auto target = profile.value(QStringLiteral("qbs.targetOS"));
+ if (target.isValid())
+ return target.toStringList().contains(os);
+ const auto host = profile.value(QStringLiteral("qbs.hostOS"));
+ if (host.isValid())
+ return host.toStringList().contains(os);
+ return Internal::contains(HostOsInfo::hostOSIdentifiers(), os.toStdString());
+}
+
static Profile createGccProfile(const QString &compilerFilePath, Settings *settings,
const QStringList &toolchainTypes,
const QString &profileName = QString())
@@ -211,8 +223,7 @@ static Profile createGccProfile(const QString &compilerFilePath, Settings *setti
toolPathSetup.apply(QLatin1String("ar"), QLatin1String("cpp.archiverPath"));
toolPathSetup.apply(QLatin1String("as"), QLatin1String("cpp.assemblerPath"));
toolPathSetup.apply(QLatin1String("nm"), QLatin1String("cpp.nmPath"));
- if (profile.value(QStringLiteral("qbs.targetOS"))
- .toStringList().contains(QStringLiteral("darwin")))
+ if (doesProfileTargetOS(profile, QStringLiteral("darwin")))
toolPathSetup.apply(QLatin1String("dsymutil"), QLatin1String("cpp.dsymutilPath"));
else
toolPathSetup.apply(QLatin1String("objcopy"), QLatin1String("cpp.objcopyPath"));
diff --git a/src/app/qbs/commandlinefrontend.cpp b/src/app/qbs/commandlinefrontend.cpp
index 4a5c2f474..261863c38 100644
--- a/src/app/qbs/commandlinefrontend.cpp
+++ b/src/app/qbs/commandlinefrontend.cpp
@@ -121,6 +121,7 @@ void CommandLineFrontend::start()
case StatusCommandType:
case InstallCommandType:
case DumpNodesTreeCommandType:
+ case ListProductsCommandType:
if (m_parser.buildConfigurations().count() > 1) {
QString error = Tr::tr("Invalid use of command '%1': There can be only one "
"build configuration.\n").arg(m_parser.commandName());
@@ -149,8 +150,10 @@ void CommandLineFrontend::start()
params.setSettingsDirectory(m_settings->baseDirectory());
params.setOverrideBuildGraphData(m_parser.command() == ResolveCommandType);
params.setPropertyCheckingMode(ErrorHandlingMode::Strict);
- if (!m_parser.buildBeforeInstalling() || m_parser.command() == DumpNodesTreeCommandType)
+ if (!m_parser.buildBeforeInstalling() || m_parser.command() == DumpNodesTreeCommandType
+ || m_parser.command() == CleanCommandType) {
params.setRestoreBehavior(SetupProjectParameters::RestoreOnly);
+ }
foreach (const QVariantMap &buildConfig, m_parser.buildConfigurations()) {
QVariantMap userConfig = buildConfig;
const QString configurationKey = QLatin1String("qbs.configurationName");
@@ -394,6 +397,10 @@ void CommandLineFrontend::handleProjectsResolved()
dumpNodesTree();
qApp->quit();
break;
+ case ListProductsCommandType:
+ listProducts();
+ qApp->quit();
+ break;
case HelpCommandType:
Q_ASSERT_X(false, Q_FUNC_INFO, "Impossible.");
}
@@ -542,6 +549,16 @@ void CommandLineFrontend::dumpNodesTree()
throw error;
}
+void CommandLineFrontend::listProducts()
+{
+ const QList<ProductData> products = productsToUse().begin().value();
+ QStringList output;
+ for (const ProductData &p : products)
+ output += p.fullDisplayName();
+ output.sort();
+ qbsInfo() << output.join(QLatin1Char('\n'));
+}
+
void CommandLineFrontend::connectBuildJobs()
{
foreach (AbstractJob * const job, m_buildJobs)
diff --git a/src/app/qbs/commandlinefrontend.h b/src/app/qbs/commandlinefrontend.h
index e10240773..ab37f6c8a 100644
--- a/src/app/qbs/commandlinefrontend.h
+++ b/src/app/qbs/commandlinefrontend.h
@@ -92,6 +92,7 @@ private:
int runTarget();
void updateTimestamps();
void dumpNodesTree();
+ void listProducts();
void connectBuildJobs();
void connectBuildJob(AbstractJob *job);
void connectJob(AbstractJob *job);
diff --git a/src/app/qbs/parser/commandlineoption.cpp b/src/app/qbs/parser/commandlineoption.cpp
index 8689b2032..011a2c891 100644
--- a/src/app/qbs/parser/commandlineoption.cpp
+++ b/src/app/qbs/parser/commandlineoption.cpp
@@ -422,20 +422,6 @@ void LogLevelOption::doParse(const QString &representation, QStringList &input)
.arg(representation, levelString, description(command())));
}
-QString ForceOption::description(CommandType command) const
-{
- Q_UNUSED(command);
- return Tr::tr("%1\n\tDisregard objections.\n"
- "\tqbs might refuse to execute a given command because certain\n"
- "\tcircumstances make it seem dubious. This option switches the\n"
- "\trespective checks off.\n").arg(longRepresentation());
-}
-
-QString ForceOption::longRepresentation() const
-{
- return QLatin1String("--force");
-}
-
QString ForceTimeStampCheckOption::description(CommandType command) const
{
Q_UNUSED(command);
diff --git a/src/app/qbs/parser/commandlineoption.h b/src/app/qbs/parser/commandlineoption.h
index cf16b475f..f99ceb896 100644
--- a/src/app/qbs/parser/commandlineoption.h
+++ b/src/app/qbs/parser/commandlineoption.h
@@ -63,7 +63,6 @@ public:
ProductsOptionType,
NoInstallOptionType,
InstallRootOptionType, RemoveFirstOptionType, NoBuildOptionType,
- ForceOptionType,
ForceTimestampCheckOptionType,
ForceOutputCheckOptionType,
BuildNonDefaultOptionType,
@@ -235,13 +234,6 @@ public:
QString longRepresentation() const;
};
-class ForceOption : public OnOffOption
-{
- QString description(CommandType command) const;
- QString shortRepresentation() const { return QString(); }
- QString longRepresentation() const;
-};
-
class ForceTimeStampCheckOption : public OnOffOption
{
QString description(CommandType command) const;
diff --git a/src/app/qbs/parser/commandlineoptionpool.cpp b/src/app/qbs/parser/commandlineoptionpool.cpp
index 2fc3e0e19..0e873c69a 100644
--- a/src/app/qbs/parser/commandlineoptionpool.cpp
+++ b/src/app/qbs/parser/commandlineoptionpool.cpp
@@ -98,9 +98,6 @@ CommandLineOption *CommandLineOptionPool::getOption(CommandLineOption::Type type
case CommandLineOption::NoBuildOptionType:
option = new NoBuildOption;
break;
- case CommandLineOption::ForceOptionType:
- option = new ForceOption;
- break;
case CommandLineOption::ForceTimestampCheckOptionType:
option = new ForceTimeStampCheckOption;
break;
@@ -215,11 +212,6 @@ NoBuildOption *CommandLineOptionPool::noBuildOption() const
return static_cast<NoBuildOption *>(getOption(CommandLineOption::NoBuildOptionType));
}
-ForceOption *CommandLineOptionPool::forceOption() const
-{
- return static_cast<ForceOption *>(getOption(CommandLineOption::ForceOptionType));
-}
-
ForceTimeStampCheckOption *CommandLineOptionPool::forceTimestampCheckOption() const
{
return static_cast<ForceTimeStampCheckOption *>(
diff --git a/src/app/qbs/parser/commandlineoptionpool.h b/src/app/qbs/parser/commandlineoptionpool.h
index 74fd8e8c1..064911e32 100644
--- a/src/app/qbs/parser/commandlineoptionpool.h
+++ b/src/app/qbs/parser/commandlineoptionpool.h
@@ -67,7 +67,6 @@ public:
InstallRootOption *installRootOption() const;
RemoveFirstOption *removeFirstoption() const;
NoBuildOption *noBuildOption() const;
- ForceOption *forceOption() const;
ForceTimeStampCheckOption *forceTimestampCheckOption() const;
ForceOutputCheckOption *forceOutputCheckOption() const;
BuildNonDefaultOption *buildNonDefaultOption() const;
diff --git a/src/app/qbs/parser/commandlineparser.cpp b/src/app/qbs/parser/commandlineparser.cpp
index 9afbd7002..d57141a11 100644
--- a/src/app/qbs/parser/commandlineparser.cpp
+++ b/src/app/qbs/parser/commandlineparser.cpp
@@ -125,7 +125,7 @@ void CommandLineParser::printHelp() const
Q_ASSERT(d->command == d->commandPool.getCommand(HelpCommandType));
const HelpCommand * const helpCommand = static_cast<HelpCommand *>(d->command);
if (helpCommand->commandToDescribe().isEmpty()) {
- stream << "qbs " QBS_VERSION "\n";
+ stream << "Qbs " QBS_VERSION ", a cross-platform build tool.\n";
stream << d->generalHelp();
} else {
const Command * const commandToDescribe
@@ -204,11 +204,6 @@ InstallOptions CommandLineParser::installOptions(const QString &profile) const
return options;
}
-bool CommandLineParser::force() const
-{
- return d->optionPool.forceOption()->enabled();
-}
-
bool CommandLineParser::forceTimestampCheck() const
{
return d->optionPool.forceTimestampCheckOption()->enabled();
@@ -373,6 +368,7 @@ QList<Command *> CommandLineParser::CommandLineParserPrivate::allCommands() cons
<< commandPool.getCommand(UpdateTimestampsCommandType)
<< commandPool.getCommand(InstallCommandType)
<< commandPool.getCommand(DumpNodesTreeCommandType)
+ << commandPool.getCommand(ListProductsCommandType)
<< commandPool.getCommand(HelpCommandType);
}
@@ -597,7 +593,7 @@ bool CommandLineParser::CommandLineParserPrivate::withNonDefaultProducts() const
bool CommandLineParser::CommandLineParserPrivate::dryRun() const
{
- if (command->type() == GenerateCommandType)
+ if (command->type() == GenerateCommandType || command->type() == ListProductsCommandType)
return true;
return optionPool.dryRunOption()->enabled();
}
diff --git a/src/app/qbs/parser/commandlineparser.h b/src/app/qbs/parser/commandlineparser.h
index da9bce31d..35f52ecad 100644
--- a/src/app/qbs/parser/commandlineparser.h
+++ b/src/app/qbs/parser/commandlineparser.h
@@ -70,7 +70,6 @@ public:
CleanOptions cleanOptions(const QString &profile) const;
GenerateOptions generateOptions() const;
InstallOptions installOptions(const QString &profile) const;
- bool force() const;
bool forceTimestampCheck() const;
bool forceOutputCheck() const;
bool dryRun() const;
diff --git a/src/app/qbs/parser/commandpool.cpp b/src/app/qbs/parser/commandpool.cpp
index 77aa043d9..38114132c 100644
--- a/src/app/qbs/parser/commandpool.cpp
+++ b/src/app/qbs/parser/commandpool.cpp
@@ -86,6 +86,9 @@ qbs::Command *CommandPool::getCommand(CommandType type) const
case DumpNodesTreeCommandType:
command = new DumpNodesTreeCommand(m_optionPool);
break;
+ case ListProductsCommandType:
+ command = new ListProductsCommand(m_optionPool);
+ break;
case HelpCommandType:
command = new HelpCommand(m_optionPool);
break;
diff --git a/src/app/qbs/parser/commandtype.h b/src/app/qbs/parser/commandtype.h
index 0f7040670..e1df18fb8 100644
--- a/src/app/qbs/parser/commandtype.h
+++ b/src/app/qbs/parser/commandtype.h
@@ -44,7 +44,7 @@ namespace qbs {
enum CommandType {
ResolveCommandType, BuildCommandType, CleanCommandType, RunCommandType, ShellCommandType,
StatusCommandType, UpdateTimestampsCommandType, DumpNodesTreeCommandType,
- InstallCommandType, HelpCommandType, GenerateCommandType
+ InstallCommandType, HelpCommandType, GenerateCommandType, ListProductsCommandType,
};
} // namespace qbs
diff --git a/src/app/qbs/parser/parsercommand.cpp b/src/app/qbs/parser/parsercommand.cpp
index e08c28c3a..2ded8a458 100644
--- a/src/app/qbs/parser/parsercommand.cpp
+++ b/src/app/qbs/parser/parsercommand.cpp
@@ -202,8 +202,7 @@ static QList<CommandLineOption::Type> resolveOptions()
<< CommandLineOption::JobsOptionType
<< CommandLineOption::DryRunOptionType
<< CommandLineOption::ForceProbesOptionType
- << CommandLineOption::LogTimeOptionType
- << CommandLineOption::ForceOptionType;
+ << CommandLineOption::LogTimeOptionType;
}
QList<CommandLineOption::Type> ResolveCommand::supportedOptions() const
@@ -294,9 +293,9 @@ QString CleanCommand::shortDescription() const
QString CleanCommand::longDescription() const
{
QString description = Tr::tr(
- "qbs %1 [options] [[configuration-name] [property:value] ...] ...\n")
+ "qbs %1 [options] [configuration-name] ...\n")
.arg(representation());
- description += Tr::tr("Removes build artifacts for the given project and configuration(s).\n");
+ description += Tr::tr("Removes build artifacts for the given configuration(s).\n");
return description += supportedOptionsDescription();
}
@@ -307,12 +306,28 @@ QString CleanCommand::representation() const
QList<CommandLineOption::Type> CleanCommand::supportedOptions() const
{
- QList<CommandLineOption::Type> options = buildOptions();
- options.removeOne(CommandLineOption::ChangedFilesOptionType);
- options.removeOne(CommandLineOption::JobsOptionType);
- options.removeOne(CommandLineOption::BuildNonDefaultOptionType);
- options.removeOne(CommandLineOption::CommandEchoModeOptionType);
- return options;
+ return QList<CommandLineOption::Type>{
+ CommandLineOption::BuildDirectoryOptionType,
+ CommandLineOption::DryRunOptionType,
+ CommandLineOption::KeepGoingOptionType,
+ CommandLineOption::LogTimeOptionType,
+ CommandLineOption::ProductsOptionType,
+ CommandLineOption::QuietOptionType,
+ CommandLineOption::SettingsDirOptionType,
+ CommandLineOption::ShowProgressOptionType,
+ CommandLineOption::VerboseOptionType,
+ };
+}
+
+void CleanCommand::parseMore(QStringList &input)
+{
+ addAllToAdditionalArguments(input);
+ for (const QString &param : additionalArguments()) {
+ if (param.contains(QLatin1Char(':'))) {
+ throw ErrorInfo(Tr::tr("The '%1' command does not support property assignments.")
+ .arg(representation()));
+ }
+ }
}
QString InstallCommand::shortDescription() const
@@ -524,6 +539,30 @@ QList<CommandLineOption::Type> DumpNodesTreeCommand::supportedOptions() const
<< CommandLineOption::ProductsOptionType;
}
+QString ListProductsCommand::shortDescription() const
+{
+ return Tr::tr("Lists all products in the project, including sub-projects.");
+}
+
+QString ListProductsCommand::longDescription() const
+{
+ QString description = Tr::tr("qbs %1 [options] [[configuration-name] "
+ "[property:value] ...] ...\n").arg(representation());
+ return description += supportedOptionsDescription();
+}
+
+QString ListProductsCommand::representation() const
+{
+ return QLatin1String("list-products");
+}
+
+QList<CommandLineOption::Type> ListProductsCommand::supportedOptions() const
+{
+ return QList<CommandLineOption::Type>()
+ << CommandLineOption::FileOptionType
+ << CommandLineOption::BuildDirectoryOptionType;
+}
+
QString HelpCommand::shortDescription() const
{
return Tr::tr("Show general or command-specific help.");
diff --git a/src/app/qbs/parser/parsercommand.h b/src/app/qbs/parser/parsercommand.h
index 151626429..92b49dadd 100644
--- a/src/app/qbs/parser/parsercommand.h
+++ b/src/app/qbs/parser/parsercommand.h
@@ -128,6 +128,7 @@ private:
QString longDescription() const;
QString representation() const;
QList<CommandLineOption::Type> supportedOptions() const;
+ void parseMore(QStringList &input);
};
class InstallCommand : public Command
@@ -213,6 +214,19 @@ private:
QList<CommandLineOption::Type> supportedOptions() const Q_DECL_OVERRIDE;
};
+class ListProductsCommand : public Command
+{
+public:
+ ListProductsCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {}
+
+private:
+ CommandType type() const override { return ListProductsCommandType; }
+ QString shortDescription() const override;
+ QString longDescription() const override;
+ QString representation() const override;
+ QList<CommandLineOption::Type> supportedOptions() const override;
+};
+
class HelpCommand : public Command
{
public:
diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp
index f6978ffc8..fc5efc1da 100644
--- a/src/lib/corelib/api/internaljobs.cpp
+++ b/src/lib/corelib/api/internaljobs.cpp
@@ -75,7 +75,7 @@ public:
void cancel() { m_canceled = true; }
private:
- void initialize(const QString &task, int maximum)
+ void initialize(const QString &task, int maximum) override
{
QBS_ASSERT(!m_timedLogger, delete m_timedLogger);
if (m_job->timed())
@@ -86,13 +86,13 @@ private:
emit m_job->newTaskStarted(task, maximum, m_job);
}
- void setMaximum(int maximum)
+ void setMaximum(int maximum) override
{
m_maximum = maximum;
emit m_job->totalEffortChanged(maximum, m_job);
}
- void setProgressValue(int value)
+ void setProgressValue(int value) override
{
//QBS_ASSERT(value >= m_value, qDebug("old value = %d, new value = %d", m_value, value));
//QBS_ASSERT(value <= m_maximum, qDebug("value = %d, maximum = %d", value, m_maximum));
@@ -104,9 +104,9 @@ private:
emit m_job->taskProgress(value, m_job);
}
- int progressValue() { return m_value; }
- int maximum() const { return m_maximum; }
- bool canceled() const { return m_canceled; }
+ int progressValue() override { return m_value; }
+ int maximum() const override { return m_maximum; }
+ bool canceled() const override { return m_canceled; }
int m_value;
int m_maximum;
diff --git a/src/lib/corelib/api/jobs.cpp b/src/lib/corelib/api/jobs.cpp
index 76c1484cf..85c0d2399 100644
--- a/src/lib/corelib/api/jobs.cpp
+++ b/src/lib/corelib/api/jobs.cpp
@@ -44,6 +44,7 @@
#include <tools/launcherinterface.h>
#include <tools/qbsassert.h>
+#include <QtCore/qloggingcategory.h>
#include <QtCore/qtimer.h>
namespace qbs {
@@ -217,6 +218,8 @@ void AbstractJob::handleFinished()
SetupProjectJob::SetupProjectJob(const Logger &logger, QObject *parent)
: AbstractJob(new InternalJobThreadWrapper(new InternalSetupProjectJob(logger)), parent)
{
+ if (logger.logSink()->logLevel() == LoggerDebug || logger.logSink()->logLevel() == LoggerTrace)
+ QLoggingCategory::setFilterRules(QStringLiteral("qbs.*.debug=true"));
}
/*!
diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp
index 88f2c689e..19b2072f9 100644
--- a/src/lib/corelib/api/project.cpp
+++ b/src/lib/corelib/api/project.cpp
@@ -463,11 +463,11 @@ ProjectPrivate::FileListUpdateContext ProjectPrivate::getFileListContext(const P
}
static SourceArtifactPtr createSourceArtifact(const QString &filePath,
- const ResolvedProductPtr &product, const GroupPtr &group, bool wildcard, Logger &logger)
+ const ResolvedProductPtr &product, const GroupPtr &group, bool wildcard)
{
const SourceArtifactPtr artifact
= ProjectResolver::createSourceArtifact(product, filePath, group, wildcard);
- ProjectResolver::applyFileTaggers(artifact, product, logger);
+ ProjectResolver::applyFileTaggers(artifact, product);
return artifact;
}
@@ -505,18 +505,18 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group
const GroupPtr &resolvedGroup = groupContext.resolvedGroups.at(i);
for (const QString &file : qAsConst(filesContext.absoluteFilePaths)) {
const SourceArtifactPtr sa = createSourceArtifact(file, resolvedProduct, resolvedGroup,
- false, logger);
+ false);
addedSourceArtifacts.insert(file, std::make_pair(sa, resolvedProduct));
}
for (const QString &file : qAsConst(filesContext.absoluteFilePathsFromWildcards)) {
QBS_CHECK(resolvedGroup->wildcards);
const SourceArtifactPtr sa = createSourceArtifact(file, resolvedProduct, resolvedGroup,
- true, logger);
+ true);
addedSourceArtifacts.insert(file, std::make_pair(sa, resolvedProduct));
}
if (resolvedProduct->enabled) {
for (const auto &pair : qAsConst(addedSourceArtifacts))
- createArtifact(resolvedProduct, pair.first, logger);
+ createArtifact(resolvedProduct, pair.first);
}
}
doSanityChecks(internalProject, logger);
@@ -1097,13 +1097,6 @@ QVariantMap Project::projectConfiguration() const
return d->internalProject->buildConfiguration();
}
-QHash<QString, QString> Project::usedEnvironment() const
-{
- typedef QHash<QString, QString> EnvType;
- QBS_ASSERT(isValid(), return EnvType());
- return d->internalProject->usedEnvironment;
-}
-
std::set<QString> Project::buildSystemFiles() const
{
QBS_ASSERT(isValid(), return std::set<QString>());
diff --git a/src/lib/corelib/api/project.h b/src/lib/corelib/api/project.h
index 034db2625..1b80d59cf 100644
--- a/src/lib/corelib/api/project.h
+++ b/src/lib/corelib/api/project.h
@@ -130,7 +130,6 @@ public:
bool recursive, const QStringList &tags = QStringList()) const;
QVariantMap projectConfiguration() const;
- QHash<QString, QString> usedEnvironment() const;
std::set<QString> buildSystemFiles() const;
diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp
index 7a2c5a83b..b5e1ddd9d 100644
--- a/src/lib/corelib/api/projectdata.cpp
+++ b/src/lib/corelib/api/projectdata.cpp
@@ -40,6 +40,7 @@
#include "projectdata_p.h"
#include "propertymap_p.h"
+#include <language/language.h>
#include <language/propertymapinternal.h>
#include <tools/fileinfo.h>
#include <tools/jsliterals.h>
@@ -451,6 +452,16 @@ QString ProductData::name() const
}
/*!
+ The name of the product as given in the qbs source file, plus information
+ about which properties it was multiplexed on and the values of these properties.
+ If the product was not multiplexed, the returned value is the same as \c name().
+ */
+QString ProductData::fullDisplayName() const
+{
+ return Internal::ResolvedProduct::fullDisplayName(name(), multiplexConfigurationId());
+}
+
+/*!
* \brief The base name of the product's target file as given in the qbs source file.
*/
QString ProductData::targetName() const
diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h
index 335c7c4f3..91bcf7d9d 100644
--- a/src/lib/corelib/api/projectdata.h
+++ b/src/lib/corelib/api/projectdata.h
@@ -186,6 +186,7 @@ public:
QStringList type() const;
QStringList dependencies() const;
QString name() const;
+ QString fullDisplayName() const;
QString targetName() const;
QString version() const;
QString profile() const;
diff --git a/src/lib/corelib/buildgraph/artifactcleaner.cpp b/src/lib/corelib/buildgraph/artifactcleaner.cpp
index b35120c55..7a4b4dd82 100644
--- a/src/lib/corelib/buildgraph/artifactcleaner.cpp
+++ b/src/lib/corelib/buildgraph/artifactcleaner.cpp
@@ -126,7 +126,7 @@ public:
bool hasError() const { return m_hasError; }
private:
- void doVisit(Artifact *artifact)
+ void doVisit(Artifact *artifact) override
{
if (m_observer->canceled())
throw ErrorInfo(Tr::tr("Cleaning up was canceled."));
diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp
index a12ef3410..58192d3df 100644
--- a/src/lib/corelib/buildgraph/buildgraph.cpp
+++ b/src/lib/corelib/buildgraph/buildgraph.cpp
@@ -42,6 +42,7 @@
#include "cycledetector.h"
#include "projectbuilddata.h"
#include "productbuilddata.h"
+#include "rulenode.h"
#include "transformer.h"
#include <jsextensions/jsextensions.h>
@@ -51,6 +52,7 @@
#include <language/propertymapinternal.h>
#include <language/resolvedfilecontext.h>
#include <language/scriptengine.h>
+#include <logging/categories.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/error.h>
@@ -283,7 +285,7 @@ static void setupProductScriptValue(ScriptEngine *engine, QScriptValue &productS
}
}
-void setupScriptEngineForFile(ScriptEngine *engine, const ResolvedFileContextConstPtr &fileContext,
+void setupScriptEngineForFile(ScriptEngine *engine, const FileContextBaseConstPtr &fileContext,
QScriptValue targetObject)
{
engine->import(fileContext, targetObject);
@@ -332,17 +334,12 @@ bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList<BuildGraphNode *> &pat
}
/*
- * c must be built before p
- * p ----> c
- * p.children = c
- * c.parents = p
- *
- * also: children means i depend on or i am produced by
- * parent means "produced by me" or "depends on me"
+ * Creates the build graph edge p -> c, which represents the dependency "c must be built before p".
*/
void connect(BuildGraphNode *p, BuildGraphNode *c)
{
QBS_CHECK(p != c);
+ qCDebug(lcBuildGraph) << "connect" << p->toString() << "->" << c->toString();
if (Artifact *ac = dynamic_cast<Artifact *>(c)) {
for (const Artifact *child : filterByType<Artifact>(p->children)) {
if (child != ac && child->filePath() == ac->filePath()) {
@@ -358,16 +355,6 @@ void connect(BuildGraphNode *p, BuildGraphNode *c)
p->product->topLevelProject()->buildData->isDirty = true;
}
-void loggedConnect(BuildGraphNode *u, BuildGraphNode *v, const Logger &logger)
-{
- QBS_CHECK(u != v);
- if (logger.traceEnabled()) {
- logger.qbsTrace() << QString::fromLatin1("[BG] connect '%1' -> '%2'")
- .arg(u->toString(), v->toString());
- }
- connect(u, v);
-}
-
static bool existsPath_impl(BuildGraphNode *u, BuildGraphNode *v, NodeSet *seen)
{
if (u == v)
@@ -398,18 +385,16 @@ static QStringList toStringList(const QList<BuildGraphNode *> &path)
return lst;
}
-bool safeConnect(Artifact *u, Artifact *v, const Logger &logger)
+bool safeConnect(Artifact *u, Artifact *v)
{
QBS_CHECK(u != v);
- if (logger.traceEnabled()) {
- logger.qbsTrace() << QString::fromLatin1("[BG] safeConnect: '%1' '%2'")
- .arg(relativeArtifactFileName(u), relativeArtifactFileName(v));
- }
+ qCDebug(lcBuildGraph) << "safeConnect:" << relativeArtifactFileName(u)
+ << "->" << relativeArtifactFileName(v);
if (existsPath(v, u)) {
QList<BuildGraphNode *> circle;
findPath(v, u, circle);
- logger.qbsTrace() << "[BG] safeConnect: circle detected " << toStringList(circle);
+ qCDebug(lcBuildGraph) << "safeConnect: circle detected " << toStringList(circle);
return false;
}
@@ -417,12 +402,9 @@ bool safeConnect(Artifact *u, Artifact *v, const Logger &logger)
return true;
}
-void disconnect(BuildGraphNode *u, BuildGraphNode *v, const Logger &logger)
+void disconnect(BuildGraphNode *u, BuildGraphNode *v)
{
- if (logger.traceEnabled()) {
- logger.qbsTrace() << QString::fromLatin1("[BG] disconnect: '%1' '%2'")
- .arg(u->toString(), v->toString());
- }
+ qCDebug(lcBuildGraph) << "disconnect:" << u->toString() << v->toString();
u->children.remove(v);
v->parents.remove(u);
u->onChildDisconnected(v);
@@ -503,19 +485,20 @@ Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact
}
Artifact *createArtifact(const ResolvedProductPtr &product,
- const SourceArtifactConstPtr &sourceArtifact, const Logger &logger)
+ const SourceArtifactConstPtr &sourceArtifact)
{
Artifact *artifact = new Artifact;
artifact->artifactType = Artifact::SourceFile;
artifact->setFilePath(sourceArtifact->absoluteFilePath);
artifact->setFileTags(sourceArtifact->fileTags);
artifact->properties = sourceArtifact->properties;
- insertArtifact(product, artifact, logger);
+ insertArtifact(product, artifact);
return artifact;
}
-void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const Logger &logger)
+void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact)
{
+ qCDebug(lcBuildGraph) << "insert artifact" << artifact->filePath();
QBS_CHECK(!artifact->product);
QBS_CHECK(!artifact->filePath().isEmpty());
QBS_CHECK(!product->buildData->nodes.contains(artifact));
@@ -524,33 +507,26 @@ void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const
product->topLevelProject()->buildData->isDirty = true;
product->buildData->nodes.insert(artifact);
addArtifactToSet(artifact, product->buildData->artifactsByFileTag);
-
- if (logger.traceEnabled()) {
- logger.qbsTrace() << QString::fromLatin1("[BG] insert artifact '%1'")
- .arg(artifact->filePath());
- }
}
static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
const Set<ResolvedProductPtr> &allProducts, const Logger &logger)
{
- logger.qbsTrace() << "Sanity checking product '" << product->uniqueName() << "'";
+ qCDebug(lcBuildGraph) << "Sanity checking product" << product->uniqueName();
CycleDetector cycleDetector(logger);
cycleDetector.visitProduct(product);
const ProductBuildData * const buildData = product->buildData.data();
- if (logger.traceEnabled())
- logger.qbsTrace() << "enabled: " << product->enabled << "; build data: " << buildData;
+ qCDebug(lcBuildGraph) << "enabled:" << product->enabled << "build data:" << buildData;
QBS_CHECK(!!product->enabled == !!buildData);
if (!product->enabled)
return;
for (BuildGraphNode * const node : qAsConst(buildData->roots)) {
- if (logger.traceEnabled())
- logger.qbsTrace() << "Checking root node '" << node->toString() << "'.";
+ qCDebug(lcBuildGraph) << "Checking root node" << node->toString();
QBS_CHECK(buildData->nodes.contains(node));
}
Set<QString> filePaths;
for (BuildGraphNode * const node : qAsConst(buildData->nodes)) {
- logger.qbsTrace() << "Sanity checking node '" << node->toString() << "'";
+ qCDebug(lcBuildGraph) << "Sanity checking node" << node->toString();
QBS_CHECK(node->product == product);
for (const BuildGraphNode * const parent : qAsConst(node->parents))
QBS_CHECK(parent->children.contains(node));
@@ -563,8 +539,16 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
}
Artifact * const artifact = dynamic_cast<Artifact *>(node);
- if (!artifact)
+ if (!artifact) {
+ RuleNode * const ruleNode = dynamic_cast<RuleNode *>(node);
+ QBS_CHECK(ruleNode);
+ QBS_CHECK(ruleNode->rule());
+ QBS_CHECK(ruleNode->rule()->product);
+ QBS_CHECK(ruleNode->rule()->product == ruleNode->product.get());
+ QBS_CHECK(ruleNode->rule()->product == product.get());
+ QBS_CHECK(product->rules.contains(std::const_pointer_cast<Rule>(ruleNode->rule())));
continue;
+ }
QBS_CHECK(!filePaths.contains(artifact->filePath()));
filePaths << artifact->filePath();
@@ -576,9 +560,14 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
continue;
QBS_CHECK(transformer);
+ QBS_CHECK(transformer->rule);
+ QBS_CHECK(transformer->rule->product);
+ QBS_CHECK(transformer->rule->product == artifact->product.get());
+ QBS_CHECK(transformer->rule->product == product.get());
QBS_CHECK(transformer->outputs.contains(artifact));
- logger.qbsTrace() << "The transformer has " << transformer->outputs.count()
- << " outputs.";
+ QBS_CHECK(product->rules.contains(std::const_pointer_cast<Rule>(transformer->rule)));
+ qCDebug(lcBuildGraph)
+ << "The transformer has" << transformer->outputs.count() << "outputs.";
ArtifactSet transformerOutputChildren;
for (const Artifact * const output : qAsConst(transformer->outputs)) {
QBS_CHECK(output->transformer == transformer);
@@ -592,13 +581,13 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
}
}
}
- if (logger.traceEnabled()) {
- logger.qbsTrace() << "The transformer output children are:";
+ if (lcBuildGraph().isDebugEnabled()) {
+ qCDebug(lcBuildGraph) << "The transformer output children are:";
for (const Artifact * const a : qAsConst(transformerOutputChildren))
- logger.qbsTrace() << "\t" << a->fileName();
- logger.qbsTrace() << "The transformer inputs are:";
+ qCDebug(lcBuildGraph) << "\t" << a->fileName();
+ qCDebug(lcBuildGraph) << "The transformer inputs are:";
for (const Artifact * const a : qAsConst(transformer->inputs))
- logger.qbsTrace() << "\t" << a->fileName();
+ qCDebug(lcBuildGraph) << "\t" << a->fileName();
}
QBS_CHECK(transformer->inputs.count() <= transformerOutputChildren.count());
for (Artifact * const transformerInput : qAsConst(transformer->inputs))
diff --git a/src/lib/corelib/buildgraph/buildgraph.h b/src/lib/corelib/buildgraph/buildgraph.h
index 0620cebb2..50e8d4776 100644
--- a/src/lib/corelib/buildgraph/buildgraph.h
+++ b/src/lib/corelib/buildgraph/buildgraph.h
@@ -68,21 +68,20 @@ Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact
bool compareByName);
Artifact *createArtifact(const ResolvedProductPtr &product,
- const SourceArtifactConstPtr &sourceArtifact, const Logger &logger);
-void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const Logger &logger);
+ const SourceArtifactConstPtr &sourceArtifact);
+void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact);
void dumpProductBuildData(const ResolvedProductConstPtr &product);
bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList<BuildGraphNode*> &path);
void QBS_AUTOTEST_EXPORT connect(BuildGraphNode *p, BuildGraphNode *c);
-void loggedConnect(BuildGraphNode *u, BuildGraphNode *v, const Logger &logger);
-bool safeConnect(Artifact *u, Artifact *v, const Logger &logger);
+bool safeConnect(Artifact *u, Artifact *v);
void removeGeneratedArtifactFromDisk(Artifact *artifact, const Logger &logger);
void removeGeneratedArtifactFromDisk(const QString &filePath, const Logger &logger);
-void disconnect(BuildGraphNode *u, BuildGraphNode *v, const Logger &logger);
+void disconnect(BuildGraphNode *u, BuildGraphNode *v);
-void setupScriptEngineForFile(ScriptEngine *engine, const ResolvedFileContextConstPtr &fileContext,
+void setupScriptEngineForFile(ScriptEngine *engine, const FileContextBaseConstPtr &fileContext,
QScriptValue targetObject);
void setupScriptEngineForProduct(ScriptEngine *engine, const ResolvedProductConstPtr &product,
const ResolvedModuleConstPtr &module, QScriptValue targetObject,
diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp
index f16d80ddb..ba0e11430 100644
--- a/src/lib/corelib/buildgraph/buildgraphloader.cpp
+++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp
@@ -43,6 +43,7 @@
#include "emptydirectoriesremover.h"
#include "productbuilddata.h"
#include "projectbuilddata.h"
+#include "rulenode.h"
#include "rulecommands.h"
#include "rulesevaluationcontext.h"
#include "transformer.h"
@@ -53,18 +54,22 @@
#include <language/propertymapinternal.h>
#include <language/qualifiedid.h>
#include <language/resolvedfilecontext.h>
+#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
#include <tools/fileinfo.h>
#include <tools/persistence.h>
+#include <tools/profile.h>
#include <tools/profiling.h>
#include <tools/qbsassert.h>
#include <tools/qttools.h>
+#include <tools/settings.h>
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
#include <algorithm>
+#include <unordered_map>
namespace qbs {
namespace Internal {
@@ -163,7 +168,7 @@ void BuildGraphLoader::loadBuildGraphFromDisk()
= ProjectBuildData::deriveBuildGraphFilePath(buildDir, projectId);
PersistentPool pool(m_logger);
- m_logger.qbsDebug() << "[BG] trying to load: " << buildGraphFilePath;
+ qCDebug(lcBuildGraph) << "trying to load:" << buildGraphFilePath;
try {
pool.load(buildGraphFilePath);
} catch (const NoBuildGraphError &e) {
@@ -251,6 +256,39 @@ static void makeChangedProductsListComplete(QList<ResolvedProductPtr> &changedPr
checkProductForChangedDependency(changedProducts, seenProducts, p);
}
+static void updateProductAndRulePointers(const ResolvedProductPtr &newProduct)
+{
+ std::unordered_map<RuleConstPtr, RuleConstPtr> ruleMap;
+ for (BuildGraphNode *node : qAsConst(newProduct->buildData->nodes)) {
+ node->product = newProduct;
+ const auto findNewRule = [&ruleMap, &newProduct]
+ (const RuleConstPtr &oldRule) -> RuleConstPtr {
+ const auto it = ruleMap.find(oldRule);
+ if (it != ruleMap.cend())
+ return it->second;
+ for (const RuleConstPtr &r : qAsConst(newProduct->rules)) {
+ if (*r == *oldRule) {
+ ruleMap.insert(std::make_pair(oldRule, r));
+ return r;
+ }
+ }
+ QBS_CHECK(false);
+ return RuleConstPtr();
+ };
+ if (node->type() == BuildGraphNode::RuleNodeType) {
+ RuleNode * const ruleNode = static_cast<RuleNode *>(node);
+ ruleNode->setRule(findNewRule(ruleNode->rule()));
+ } else {
+ QBS_CHECK(node->type() == BuildGraphNode::ArtifactNodeType);
+ Artifact * const artifact = static_cast<Artifact *>(node);
+ if (artifact->artifactType == Artifact::Generated) {
+ QBS_CHECK(artifact->transformer);
+ artifact->transformer->rule = findNewRule(artifact->transformer->rule);
+ }
+ }
+ }
+}
+
void BuildGraphLoader::trackProjectChanges()
{
TimedActivityLogger trackingTimer(m_logger, Tr::tr("Change tracking"),
@@ -357,10 +395,8 @@ void BuildGraphLoader::trackProjectChanges()
if (newlyResolvedProduct->uniqueName() == restoredProduct->uniqueName()) {
if (newlyResolvedProduct->enabled)
newlyResolvedProduct->buildData.swap(restoredProduct->buildData);
- if (newlyResolvedProduct->buildData) {
- for (BuildGraphNode *node : qAsConst(newlyResolvedProduct->buildData->nodes))
- node->product = newlyResolvedProduct;
- }
+ if (newlyResolvedProduct->buildData)
+ updateProductAndRulePointers(newlyResolvedProduct);
// Keep in list if build data still needs to be resolved.
if (!newlyResolvedProduct->enabled || newlyResolvedProduct->buildData)
@@ -433,9 +469,9 @@ bool BuildGraphLoader::hasEnvironmentChanged(const TopLevelProjectConstPtr &rest
newEnv.remove(QLatin1String("LD_PRELOAD"));
if (oldEnv != newEnv) {
- m_logger.qbsDebug() << "Set of environment variables changed. Must re-resolve project.";
- m_logger.qbsTrace() << "old: " << restoredProject->environment.toStringList() << "\nnew:"
- << m_parameters.adjustedEnvironment().toStringList();
+ qCDebug(lcBuildGraph) << "Set of environment variables changed. Must re-resolve project."
+ << "\nold:" << restoredProject->environment.toStringList()
+ << "\nnew:" << m_parameters.adjustedEnvironment().toStringList();
return true;
}
return false;
@@ -446,8 +482,8 @@ bool BuildGraphLoader::hasCanonicalFilePathResultChanged(const TopLevelProjectCo
for (auto it = restoredProject->canonicalFilePathResults.constBegin();
it != restoredProject->canonicalFilePathResults.constEnd(); ++it) {
if (QFileInfo(it.key()).canonicalFilePath() != it.value()) {
- m_logger.qbsDebug() << "Canonical file path for file '" << it.key()
- << "' changed, must re-resolve project.";
+ qCDebug(lcBuildGraph) << "Canonical file path for file" << it.key()
+ << "changed, must re-resolve project.";
return true;
}
}
@@ -460,8 +496,8 @@ bool BuildGraphLoader::hasFileExistsResultChanged(const TopLevelProjectConstPtr
for (QHash<QString, bool>::ConstIterator it = restoredProject->fileExistsResults.constBegin();
it != restoredProject->fileExistsResults.constEnd(); ++it) {
if (FileInfo(it.key()).exists() != it.value()) {
- m_logger.qbsDebug() << "Existence check for file '" << it.key()
- << " 'changed, must re-resolve project.";
+ qCDebug(lcBuildGraph) << "Existence check for file" << it.key()
+ << "changed, must re-resolve project.";
return true;
}
}
@@ -475,10 +511,9 @@ bool BuildGraphLoader::hasDirectoryEntriesResultChanged(const TopLevelProjectCon
it != restoredProject->directoryEntriesResults.constEnd(); ++it) {
if (QDir(it.key().first).entryList(static_cast<QDir::Filters>(it.key().second), QDir::Name)
!= it.value()) {
- m_logger.qbsDebug() << "Entry list for directory '" << it.key().first
- << "' ("
- << static_cast<QDir::Filters>(it.key().second)
- << ") changed, must re-resolve project.";
+ qCDebug(lcBuildGraph) << "Entry list for directory" << it.key().first
+ << static_cast<QDir::Filters>(it.key().second)
+ << "changed, must re-resolve project.";
return true;
}
}
@@ -492,8 +527,8 @@ bool BuildGraphLoader::hasFileLastModifiedResultChanged(const TopLevelProjectCon
= restoredProject->fileLastModifiedResults.constBegin();
it != restoredProject->fileLastModifiedResults.constEnd(); ++it) {
if (FileInfo(it.key()).lastModified() != it.value()) {
- m_logger.qbsDebug() << "Timestamp for file '" << it.key()
- << " 'changed, must re-resolve project.";
+ qCDebug(lcBuildGraph) << "Timestamp for file" << it.key()
+ << "changed, must re-resolve project.";
return true;
}
}
@@ -511,17 +546,17 @@ bool BuildGraphLoader::hasProductFileChanged(const QList<ResolvedProductPtr> &re
const FileInfo pfi(filePath);
remainingBuildSystemFiles.remove(filePath);
if (!pfi.exists()) {
- m_logger.qbsDebug() << "A product was removed, must re-resolve project";
+ qCDebug(lcBuildGraph) << "A product was removed, must re-resolve project";
hasChanged = true;
} else if (referenceTime < pfi.lastModified()) {
- m_logger.qbsDebug() << "A product was changed, must re-resolve project";
+ qCDebug(lcBuildGraph) << "A product was changed, must re-resolve project";
hasChanged = true;
} else if (!changedProducts.contains(product)) {
bool foundMissingSourceFile = false;
for (const QString &file : qAsConst(product->missingSourceFiles)) {
if (FileInfo(file).exists()) {
- m_logger.qbsDebug() << "Formerly missing file " << file << " in product "
- << product->name << " exists now, must re-resolve project";
+ qCDebug(lcBuildGraph) << "Formerly missing file" << file << "in product"
+ << product->name << "exists now, must re-resolve project";
foundMissingSourceFile = true;
break;
}
@@ -571,7 +606,7 @@ bool BuildGraphLoader::hasBuildSystemFileChanged(const Set<QString> &buildSystem
for (const QString &file : buildSystemFiles) {
const FileInfo fi(file);
if (!fi.exists() || referenceTime < fi.lastModified()) {
- m_logger.qbsDebug() << "A qbs or js file changed, must re-resolve project.";
+ qCDebug(lcBuildGraph) << "A qbs or js file changed, must re-resolve project.";
return true;
}
}
@@ -588,16 +623,16 @@ void BuildGraphLoader::checkAllProductsForChanges(const QList<ResolvedProductPtr
if (!newlyResolvedProduct)
continue;
if (newlyResolvedProduct->enabled != restoredProduct->enabled) {
- m_logger.qbsDebug() << "Condition of product '" << restoredProduct->uniqueName()
- << "' was changed, must set up build data from scratch";
+ qCDebug(lcBuildGraph) << "Condition of product" << restoredProduct->uniqueName()
+ << "was changed, must set up build data from scratch";
if (!changedProducts.contains(restoredProduct))
changedProducts << restoredProduct;
continue;
}
if (checkProductForChanges(restoredProduct, newlyResolvedProduct)) {
- m_logger.qbsDebug() << "Product '" << restoredProduct->uniqueName()
- << "' was changed, must set up build data from scratch";
+ qCDebug(lcBuildGraph) << "Product" << restoredProduct->uniqueName()
+ << "was changed, must set up build data from scratch";
if (!changedProducts.contains(restoredProduct))
changedProducts << restoredProduct;
continue;
@@ -605,8 +640,8 @@ void BuildGraphLoader::checkAllProductsForChanges(const QList<ResolvedProductPtr
if (!sourceArtifactSetsAreEqual(restoredProduct->allEnabledFiles(),
newlyResolvedProduct->allEnabledFiles())) {
- m_logger.qbsDebug() << "File list of product '" << restoredProduct->uniqueName()
- << "' was changed.";
+ qCDebug(lcBuildGraph) << "File list of product" << restoredProduct->uniqueName()
+ << "was changed.";
if (!changedProducts.contains(restoredProduct))
changedProducts << restoredProduct;
}
@@ -636,7 +671,7 @@ bool BuildGraphLoader::checkProductForChanges(const ResolvedProductPtr &restored
// within commands).
if (checkForPropertyChanges(restoredProduct, newlyResolvedProduct))
return true;
- if (!ruleListsAreEqual(restoredProduct->rules.toList(), newlyResolvedProduct->rules.toList()))
+ if (!ruleListsAreEqual(restoredProduct->rules, newlyResolvedProduct->rules))
return true;
if (!dependenciesAreEqual(restoredProduct, newlyResolvedProduct))
return true;
@@ -659,7 +694,8 @@ bool BuildGraphLoader::checkProductForInstallInfoChanges(const ResolvedProductPt
for (const QString &key : specialProperties) {
if (restoredProduct->moduleProperties->qbsPropertyValue(key)
!= newlyResolvedProduct->moduleProperties->qbsPropertyValue(key)) {
- m_logger.qbsDebug() << "Product property 'qbs." << key << "' changed.";
+ qCDebug(lcBuildGraph).noquote().nospace()
+ << "Product property 'qbs." << key << "' changed.";
return true;
}
}
@@ -671,8 +707,8 @@ bool BuildGraphLoader::checkForPropertyChanges(const ResolvedProductPtr &restore
{
AccumulatingTimer propertyComparisonTimer(m_parameters.logElapsedTime()
? &m_propertyComparisonEffort: nullptr);
- m_logger.qbsDebug() << "Checking for changes in properties requested in prepare scripts for "
- "product '" << restoredProduct->uniqueName() << "'.";
+ qCDebug(lcBuildGraph) << "Checking for changes in properties requested in prepare scripts for "
+ "product" << restoredProduct->uniqueName();
if (!restoredProduct->buildData)
return false;
@@ -681,8 +717,8 @@ bool BuildGraphLoader::checkForPropertyChanges(const ResolvedProductPtr &restore
return true;
if (restoredProduct->fileTags != newlyResolvedProduct->fileTags) {
- m_logger.qbsTrace() << "Product type changed from " << restoredProduct->fileTags
- << "to " << newlyResolvedProduct->fileTags;
+ qCDebug(lcBuildGraph) << "Product type changed from" << restoredProduct->fileTags
+ << "to" << newlyResolvedProduct->fileTags;
return true;
}
@@ -709,8 +745,8 @@ bool BuildGraphLoader::checkTransformersForChanges(const ResolvedProductPtr &res
transformerChanges = true;
}
if (transformerChanges) {
- m_logger.qbsDebug() << "Property or environment changes in product '"
- << newlyResolvedProduct->uniqueName() << "'.";
+ qCDebug(lcBuildGraph) << "Property or environment changes in product"
+ << newlyResolvedProduct->uniqueName();
}
return transformerChanges;
}
@@ -718,7 +754,7 @@ bool BuildGraphLoader::checkTransformersForChanges(const ResolvedProductPtr &res
void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product,
ProjectBuildData *projectBuildData, bool removeArtifactsFromDisk)
{
- m_logger.qbsDebug() << "[BG] product '" << product->uniqueName() << "' removed.";
+ qCDebug(lcBuildGraph) << "product" << product->uniqueName() << "removed.";
product->project->products.removeOne(product);
if (product->buildData) {
@@ -888,10 +924,11 @@ bool BuildGraphLoader::checkForPropertyChange(const Property &restoredProperty,
}
}
if (restoredProperty.value != v) {
- m_logger.qbsDebug() << "Value for property '" << restoredProperty.moduleName << "."
- << restoredProperty.propertyName << "' has changed.";
- m_logger.qbsDebug() << "Old value was '" << restoredProperty.value << "'.";
- m_logger.qbsDebug() << "New value is '" << v << "'.";
+ qCDebug(lcBuildGraph).noquote().nospace()
+ << "Value for property '" << restoredProperty.moduleName << "."
+ << restoredProperty.propertyName << "' has changed.\n"
+ << "Old value was '" << restoredProperty.value << "'.\n"
+ << "New value is '" << v << "'.";
return true;
}
return false;
@@ -900,17 +937,14 @@ bool BuildGraphLoader::checkForPropertyChange(const Property &restoredProperty,
void BuildGraphLoader::replaceFileDependencyWithArtifact(const ResolvedProductPtr &fileDepProduct,
FileDependency *filedep, Artifact *artifact)
{
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace()
- << QString::fromLatin1("[BG] replace file dependency '%1' with artifact of type '%2'")
- .arg(filedep->filePath()).arg(artifact->artifactType);
- }
+ qCDebug(lcBuildGraph) << "replace file dependency" << filedep->filePath()
+ << "with artifact of type" << toString(artifact->artifactType);
for (const ResolvedProductPtr &product : fileDepProduct->topLevelProject()->allProducts()) {
if (!product->buildData)
continue;
for (Artifact *artifactInProduct : filterByType<Artifact>(product->buildData->nodes)) {
if (artifactInProduct->fileDependencies.remove(filedep))
- loggedConnect(artifactInProduct, artifact, m_logger);
+ connect(artifactInProduct, artifact);
}
}
fileDepProduct->topLevelProject()->buildData->fileDependencies.remove(filedep);
@@ -946,10 +980,12 @@ bool BuildGraphLoader::checkConfigCompatibility()
return true;
if (m_parameters.finalBuildConfigurationTree() != restoredProject->buildConfiguration())
return false;
+ Settings settings(m_parameters.settingsDirectory());
for (QVariantMap::ConstIterator it = restoredProject->profileConfigs.constBegin();
it != restoredProject->profileConfigs.constEnd(); ++it) {
+ const Profile profile(it.key(), &settings);
const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration(
- m_parameters.settingsDirectory(), it.key(), m_parameters.configurationName());
+ profile, m_parameters.configurationName());
const QVariantMap newConfig = SetupProjectParameters::finalBuildConfigurationTree(
buildConfig, m_parameters.overriddenValues());
if (newConfig != it.value())
@@ -964,8 +1000,8 @@ bool BuildGraphLoader::isPrepareScriptUpToDate(const ScriptFunctionConstPtr &scr
for (const JsImport &jsImport : script->fileContext->jsImports()) {
for (const QString &filePath : qAsConst(jsImport.filePaths)) {
if (FileInfo(filePath).lastModified() > referenceTime) {
- m_logger.qbsDebug() << "Change in import '" << filePath
- << "' potentially influences prepare script, marking as out of date";
+ qCDebug(lcBuildGraph) << "Change in import" << filePath
+ << "potentially influences prepare script, marking as out of date";
return false;
}
}
@@ -981,10 +1017,7 @@ void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restore
if (!restoredProduct->enabled || !newlyResolvedProduct->enabled)
return;
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << QString::fromLatin1("[BG] rescue data of product '%1'")
- .arg(restoredProduct->uniqueName());
- }
+ qCDebug(lcBuildGraph) << "rescue data of product" << restoredProduct->uniqueName();
QBS_CHECK(newlyResolvedProduct->buildData);
QBS_CHECK(newlyResolvedProduct->buildData->rescuableArtifactData.isEmpty());
newlyResolvedProduct->buildData->rescuableArtifactData = existingRad;
diff --git a/src/lib/corelib/buildgraph/depscanner.cpp b/src/lib/corelib/buildgraph/depscanner.cpp
index 2ca4c3e5f..550c39078 100644
--- a/src/lib/corelib/buildgraph/depscanner.cpp
+++ b/src/lib/corelib/buildgraph/depscanner.cpp
@@ -47,6 +47,7 @@
#include <logging/translator.h>
#include <language/language.h>
#include <language/propertymapinternal.h>
+#include <language/resolvedfilecontext.h>
#include <language/scriptengine.h>
#include <jsextensions/moduleproperties.h>
#include <plugins/scanner/scanner.h>
@@ -155,9 +156,8 @@ bool PluginDependencyScanner::areModulePropertiesCompatible(const PropertyMapCon
}
UserDependencyScanner::UserDependencyScanner(const ResolvedScannerConstPtr &scanner,
- const Logger &logger, ScriptEngine *engine)
+ ScriptEngine *engine)
: m_scanner(scanner),
- m_logger(logger),
m_engine(engine),
m_observer(m_engine),
m_product(0)
diff --git a/src/lib/corelib/buildgraph/depscanner.h b/src/lib/corelib/buildgraph/depscanner.h
index 6ab577839..43ac7f4d9 100644
--- a/src/lib/corelib/buildgraph/depscanner.h
+++ b/src/lib/corelib/buildgraph/depscanner.h
@@ -98,8 +98,7 @@ private:
class UserDependencyScanner : public DependencyScanner
{
public:
- UserDependencyScanner(const ResolvedScannerConstPtr &scanner, const Logger &logger,
- ScriptEngine *engine);
+ UserDependencyScanner(const ResolvedScannerConstPtr &scanner, ScriptEngine *engine);
private:
QStringList collectSearchPaths(Artifact *artifact);
@@ -113,7 +112,6 @@ private:
QStringList evaluate(Artifact *artifact, const ScriptFunctionPtr &script);
ResolvedScannerConstPtr m_scanner;
- Logger m_logger;
ScriptEngine *m_engine;
PrepareScriptObserver m_observer;
QScriptValue m_global;
diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp
index dc81385e2..c27c0e5cc 100644
--- a/src/lib/corelib/buildgraph/executor.cpp
+++ b/src/lib/corelib/buildgraph/executor.cpp
@@ -55,6 +55,7 @@
#include <language/language.h>
#include <language/propertymapinternal.h>
#include <language/scriptengine.h>
+#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
@@ -87,8 +88,6 @@ Executor::Executor(const Logger &logger, QObject *parent)
, m_progressObserver(0)
, m_state(ExecutorIdle)
, m_cancelationTimer(new QTimer(this))
- , m_doTrace(logger.traceEnabled())
- , m_doDebug(logger.debugEnabled())
{
m_inputArtifactScanContext = new InputArtifactScannerContext;
m_cancelationTimer->setSingleShot(false);
@@ -207,8 +206,8 @@ void Executor::doBuild()
{
if (m_buildOptions.maxJobCount() <= 0) {
m_buildOptions.setMaxJobCount(BuildOptions::defaultMaxJobCount());
- m_logger.qbsDebug() << "max job count not explicitly set, using value of "
- << m_buildOptions.maxJobCount();
+ qCDebug(lcExec) << "max job count not explicitly set, using value of"
+ << m_buildOptions.maxJobCount();
}
QBS_CHECK(m_state == ExecutorIdle);
m_leaves = Leaves();
@@ -242,7 +241,7 @@ void Executor::doBuild()
setState(ExecutorRunning);
if (m_productsToBuild.isEmpty()) {
- m_logger.qbsTrace() << "No products to build, finishing.";
+ qCDebug(lcExec) << "No products to build, finishing.";
QTimer::singleShot(0, this, &Executor::finish); // Don't call back on the caller.
return;
}
@@ -274,7 +273,7 @@ void Executor::doBuild()
setupProgressObserver();
initLeaves();
if (!scheduleJobs()) {
- m_logger.qbsTrace() << "Nothing to do at all, finishing.";
+ qCDebug(lcExec) << "Nothing to do at all, finishing.";
QTimer::singleShot(0, this, &Executor::finish); // Don't call back on the caller.
}
if (m_progressObserver)
@@ -322,8 +321,7 @@ void Executor::updateLeaves(BuildGraphNode *node, NodeSet &seenNodes)
}
if (isLeaf) {
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] adding leaf " << node->toString();
+ qCDebug(lcExec) << "adding leaf" << node->toString();
m_leaves.push(node);
}
}
@@ -346,16 +344,12 @@ bool Executor::scheduleJobs()
nodeToBuild->accept(this);
break;
case BuildGraphNode::Building:
- if (m_doDebug) {
- m_logger.qbsDebug() << "[EXEC] " << nodeToBuild->toString();
- m_logger.qbsDebug() << "[EXEC] node is currently being built. Skipping.";
- }
+ qCDebug(lcExec) << nodeToBuild->toString();
+ qCDebug(lcExec) << "node is currently being built. Skipping.";
break;
case BuildGraphNode::Built:
- if (m_doDebug) {
- m_logger.qbsDebug() << "[EXEC] " << nodeToBuild->toString();
- m_logger.qbsDebug() << "[EXEC] node already built. Skipping.";
- }
+ qCDebug(lcExec) << nodeToBuild->toString();
+ qCDebug(lcExec) << "node already built. Skipping.";
break;
}
}
@@ -366,32 +360,25 @@ bool Executor::isUpToDate(Artifact *artifact) const
{
QBS_CHECK(artifact->artifactType == Artifact::Generated);
- if (m_doDebug) {
- m_logger.qbsDebug() << "[UTD] check " << artifact->filePath() << " "
- << artifact->timestamp().toString();
- }
+ qCDebug(lcUpToDateCheck) << "check" << artifact->filePath()
+ << artifact->timestamp().toString();
if (m_buildOptions.forceTimestampCheck()) {
artifact->setTimestamp(FileInfo(artifact->filePath()).lastModified());
- if (m_doDebug) {
- m_logger.qbsDebug() << "[UTD] timestamp retrieved from filesystem: "
- << artifact->timestamp().toString();
- }
+ qCDebug(lcUpToDateCheck) << "timestamp retrieved from filesystem:"
+ << artifact->timestamp().toString();
}
if (!artifact->timestamp().isValid()) {
- if (m_doDebug)
- m_logger.qbsDebug() << "[UTD] invalid timestamp. Out of date.";
+ qCDebug(lcUpToDateCheck) << "invalid timestamp. Out of date.";
return false;
}
for (Artifact *childArtifact : filterByType<Artifact>(artifact->children)) {
QBS_CHECK(childArtifact->timestamp().isValid());
- if (m_doDebug) {
- m_logger.qbsDebug() << "[UTD] child timestamp "
- << childArtifact->timestamp().toString() << " "
- << childArtifact->filePath();
- }
+ qCDebug(lcUpToDateCheck) << "child timestamp"
+ << childArtifact->timestamp().toString()
+ << childArtifact->filePath();
if (artifact->timestamp() < childArtifact->timestamp())
return false;
}
@@ -401,18 +388,14 @@ bool Executor::isUpToDate(Artifact *artifact) const
FileInfo fi(fileDependency->filePath());
fileDependency->setTimestamp(fi.lastModified());
if (!fileDependency->timestamp().isValid()) {
- if (m_doDebug) {
- m_logger.qbsDebug() << "[UTD] file dependency doesn't exist "
- << fileDependency->filePath();
- }
+ qCDebug(lcUpToDateCheck) << "file dependency doesn't exist"
+ << fileDependency->filePath();
return false;
}
}
- if (m_doDebug) {
- m_logger.qbsDebug() << "[UTD] file dependency timestamp "
- << fileDependency->timestamp().toString() << " "
- << fileDependency->filePath();
- }
+ qCDebug(lcUpToDateCheck) << "file dependency timestamp"
+ << fileDependency->timestamp().toString()
+ << fileDependency->filePath();
if (artifact->timestamp() < fileDependency->timestamp())
return false;
}
@@ -441,8 +424,7 @@ bool Executor::mustExecuteTransformer(const TransformerPtr &transformer) const
void Executor::buildArtifact(Artifact *artifact)
{
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] " << relativeArtifactFileName(artifact);
+ qCDebug(lcExec) << relativeArtifactFileName(artifact);
QBS_CHECK(artifact->buildState == BuildGraphNode::Buildable);
@@ -457,9 +439,7 @@ void Executor::buildArtifact(Artifact *artifact)
if (artifact->artifactType == Artifact::SourceFile && !artifact->timestampRetrieved)
retrieveSourceFileTimestamp(artifact);
- if (m_doDebug)
- m_logger.qbsDebug() << QString::fromLatin1("[EXEC] artifact type %1. Skipping.")
- .arg(toString(artifact->artifactType));
+ qCDebug(lcExec) << "artifact type" << toString(artifact->artifactType) << "Skipping.";
finishArtifact(artifact);
return;
}
@@ -507,12 +487,9 @@ void Executor::executeRuleNode(RuleNode *ruleNode)
ruleNode->apply(m_logger, changedInputArtifacts, &result);
if (result.upToDate) {
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] " << ruleNode->toString()
- << " is up to date. Skipping.";
+ qCDebug(lcExec) << ruleNode->toString() << "is up to date. Skipping.";
} else {
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] " << ruleNode->toString();
+ qCDebug(lcExec) << ruleNode->toString();
const WeakPointer<ResolvedProduct> &product = ruleNode->product;
Set<RuleNode *> parentRules;
if (!result.createdNodes.isEmpty()) {
@@ -522,9 +499,8 @@ void Executor::executeRuleNode(RuleNode *ruleNode)
}
}
for (BuildGraphNode *node : qAsConst(result.createdNodes)) {
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] rule created " << node->toString();
- loggedConnect(node, ruleNode, m_logger);
+ qCDebug(lcExec) << "rule created" << node->toString();
+ Internal::connect(node, ruleNode);
Artifact *outputArtifact = dynamic_cast<Artifact *>(node);
if (!outputArtifact)
continue;
@@ -532,10 +508,10 @@ void Executor::executeRuleNode(RuleNode *ruleNode)
product->buildData->roots += outputArtifact;
for (Artifact *inputArtifact : qAsConst(outputArtifact->transformer->inputs))
- loggedConnect(ruleNode, inputArtifact, m_logger);
+ Internal::connect(ruleNode, inputArtifact);
for (RuleNode *parentRule : qAsConst(parentRules))
- loggedConnect(parentRule, outputArtifact, m_logger);
+ Internal::connect(parentRule, outputArtifact);
}
updateLeaves(result.createdNodes);
updateLeaves(result.invalidatedNodes);
@@ -587,21 +563,21 @@ void Executor::finishJob(ExecutorJob *job, bool success)
cancelJobs();
if (m_state == ExecutorRunning && m_progressObserver && m_progressObserver->canceled()) {
- m_logger.qbsTrace() << "Received cancel request; canceling build.";
+ qCDebug(lcExec) << "Received cancel request; canceling build.";
m_explicitlyCanceled = true;
cancelJobs();
}
if (m_state == ExecutorCanceling) {
if (m_processingJobs.isEmpty()) {
- m_logger.qbsTrace() << "All pending jobs are done, finishing.";
+ qCDebug(lcExec) << "All pending jobs are done, finishing.";
finish();
}
return;
}
if (!scheduleJobs()) {
- m_logger.qbsTrace() << "Nothing left to build; finishing.";
+ qCDebug(lcExec) << "Nothing left to build; finishing.";
finish();
}
}
@@ -617,24 +593,18 @@ void Executor::finishNode(BuildGraphNode *leaf)
leaf->buildState = BuildGraphNode::Built;
for (BuildGraphNode * const parent : qAsConst(leaf->parents)) {
if (parent->buildState != BuildGraphNode::Buildable) {
- if (m_doTrace) {
- m_logger.qbsTrace() << "[EXEC] parent " << parent->toString()
- << " build state: " << toString(parent->buildState);
- }
+ qCDebug(lcExec) << "parent" << parent->toString()
+ << "build state:" << toString(parent->buildState);
continue;
}
if (allChildrenBuilt(parent)) {
m_leaves.push(parent);
- if (m_doTrace) {
- m_logger.qbsTrace() << "[EXEC] finishNode adds leaf "
- << parent->toString() << " " << toString(parent->buildState);
- }
+ qCDebug(lcExec) << "finishNode adds leaf"
+ << parent->toString() << toString(parent->buildState);
} else {
- if (m_doTrace) {
- m_logger.qbsTrace() << "[EXEC] parent " << parent->toString()
- << " build state: " << toString(parent->buildState);
- }
+ qCDebug(lcExec) << "parent" << parent->toString()
+ << "build state:" << toString(parent->buildState);
}
}
}
@@ -642,9 +612,7 @@ void Executor::finishNode(BuildGraphNode *leaf)
void Executor::finishArtifact(Artifact *leaf)
{
QBS_CHECK(leaf);
- if (m_doTrace)
- m_logger.qbsTrace() << "[EXEC] finishArtifact " << relativeArtifactFileName(leaf);
-
+ qCDebug(lcExec) << "finishArtifact" << relativeArtifactFileName(leaf);
finishNode(leaf);
}
@@ -692,7 +660,7 @@ void Executor::cancelJobs()
{
if (m_state == ExecutorCanceling)
return;
- m_logger.qbsTrace() << "Canceling all jobs.";
+ qCDebug(lcExec) << "Canceling all jobs.";
setState(ExecutorCanceling);
for (ExecutorJob *job : m_processingJobs.keys())
job->cancel();
@@ -733,8 +701,8 @@ void Executor::handleError(const ErrorInfo &error)
void Executor::addExecutorJobs()
{
- m_logger.qbsDebug() << QString::fromLatin1("[EXEC] preparing executor for %1 jobs in parallel")
- .arg(m_buildOptions.maxJobCount());
+ qCDebug(lcExec) << "preparing executor for" << m_buildOptions.maxJobCount()
+ << "jobs in parallel";
for (int i = 1; i <= m_buildOptions.maxJobCount(); i++) {
ExecutorJob *job = new ExecutorJob(m_logger, this);
job->setMainThreadScriptEngine(m_evalContext->engine());
@@ -761,16 +729,11 @@ void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0)
return;
ResolvedProduct * const product = artifact->product.get();
- AllRescuableArtifactData::Iterator it
- = product->buildData->rescuableArtifactData.find(artifact->filePath());
- if (it == product->buildData->rescuableArtifactData.end())
+ const RescuableArtifactData rad
+ = product->buildData->rescuableArtifactData.take(artifact->filePath());
+ if (!rad.isValid())
return;
-
- const RescuableArtifactData &rad = it.value();
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << QString::fromLatin1("[BG] Attempting to rescue data of "
- "artifact '%1'").arg(artifact->fileName());
- }
+ qCDebug(lcBuildGraph) << "Attempting to rescue data of artifact" << artifact->fileName();
typedef std::pair<Artifact *, bool> ChildArtifactData;
QList<ChildArtifactData> childrenToConnect;
@@ -788,14 +751,19 @@ void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0)
// If a child has disappeared, we must re-build even if the commands
// are the same. Example: Header file included in cpp file does not exist anymore.
canRescue = false;
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "Former child artifact '" << cd.childFilePath
- << "' does not exist anymore";
- break;
+ qCDebug(lcBuildGraph) << "Former child artifact" << cd.childFilePath
+ << "does not exist anymore.";
+ const RescuableArtifactData childRad
+ = product->buildData->rescuableArtifactData.take(cd.childFilePath);
+ if (childRad.isValid()) {
+ m_artifactsRemovedFromDisk << artifact->filePath();
+ removeGeneratedArtifactFromDisk(cd.childFilePath, m_logger);
+ }
}
// TODO: Shouldn't addedByScanner always be true here? Otherwise the child would be
// in the list already, no?
- childrenToConnect << std::make_pair(child, cd.addedByScanner);
+ if (canRescue)
+ childrenToConnect << std::make_pair(child, cd.addedByScanner);
}
if (canRescue) {
@@ -805,13 +773,11 @@ void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0)
QBS_CHECK(newChildCount >= rad.children.count());
if (newChildCount > rad.children.count()) {
canRescue = false;
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "Artifact has children not present in rescue data";
+ qCDebug(lcBuildGraph) << "Artifact has children not present in rescue data.";
}
}
} else {
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "Transformer commands changed.";
+ qCDebug(lcBuildGraph) << "Transformer commands changed.";
}
if (canRescue) {
@@ -827,19 +793,16 @@ void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0)
if (childrenAdded && !childrenToConnect.isEmpty())
*childrenAdded = true;
for (const ChildArtifactData &cad : qAsConst(childrenToConnect)) {
- safeConnect(artifact, cad.first, m_logger);
+ safeConnect(artifact, cad.first);
if (cad.second)
artifact->childrenAddedByScanner << cad.first;
}
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "Data was rescued.";
+ qCDebug(lcBuildGraph) << "Data was rescued.";
} else {
removeGeneratedArtifactFromDisk(artifact, m_logger);
m_artifactsRemovedFromDisk << artifact->filePath();
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "Data not rescued.";
+ qCDebug(lcBuildGraph) << "Data not rescued.";
}
- product->buildData->rescuableArtifactData.erase(it);
}
bool Executor::checkForUnbuiltDependencies(Artifact *artifact)
@@ -850,17 +813,11 @@ bool Executor::checkForUnbuiltDependencies(Artifact *artifact)
switch (dependency->buildState) {
case BuildGraphNode::Untouched:
case BuildGraphNode::Buildable:
- if (m_logger.debugEnabled()) {
- m_logger.qbsDebug() << "[EXEC] unbuilt dependency: "
- << dependency->toString();
- }
+ qCDebug(lcExec) << "unbuilt dependency:" << dependency->toString();
unbuiltDependencies += dependency;
break;
case BuildGraphNode::Building: {
- if (m_logger.debugEnabled()) {
- m_logger.qbsDebug() << "[EXEC] dependency in state 'Building': "
- << dependency->toString();
- }
+ qCDebug(lcExec) << "dependency in state 'Building':" << dependency->toString();
buildingDependenciesFound = true;
break;
}
@@ -893,42 +850,34 @@ void Executor::potentiallyRunTransformer(const TransformerPtr &transformer)
}
if (!transformerHasMatchingOutputTags(transformer)) {
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] file tags do not match. Skipping.";
+ qCDebug(lcExec) << "file tags do not match. Skipping.";
finishTransformer(transformer);
return;
}
if (!transformerHasMatchingInputFiles(transformer)) {
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] input files do not match. Skipping.";
+ qCDebug(lcExec) << "input files do not match. Skipping.";
finishTransformer(transformer);
return;
}
const bool mustExecute = mustExecuteTransformer(transformer);
- if (!mustExecute && !m_buildOptions.forceTimestampCheck()) {
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] Up to date. Skipping.";
- finishTransformer(transformer);
- return;
- }
-
- for (Artifact * const output : qAsConst(transformer->outputs)) {
- // Scan all input artifacts. If new dependencies were found during scanning, delay
- // execution of this transformer.
- InputArtifactScanner scanner(output, m_inputArtifactScanContext, m_logger);
- AccumulatingTimer scanTimer(m_buildOptions.logElapsedTime()
- ? &m_elapsedTimeScanners : nullptr);
- scanner.scan();
- scanTimer.stop();
- if (scanner.newDependencyAdded() && checkForUnbuiltDependencies(output))
- return;
+ if (mustExecute || m_buildOptions.forceTimestampCheck()) {
+ for (Artifact * const output : qAsConst(transformer->outputs)) {
+ // Scan all input artifacts. If new dependencies were found during scanning, delay
+ // execution of this transformer.
+ InputArtifactScanner scanner(output, m_inputArtifactScanContext, m_logger);
+ AccumulatingTimer scanTimer(m_buildOptions.logElapsedTime()
+ ? &m_elapsedTimeScanners : nullptr);
+ scanner.scan();
+ scanTimer.stop();
+ if (scanner.newDependencyAdded() && checkForUnbuiltDependencies(output))
+ return;
+ }
}
if (!mustExecute) {
- if (m_doDebug)
- m_logger.qbsDebug() << "[EXEC] Up to date. Skipping.";
+ qCDebug(lcExec) << "Up to date. Skipping.";
finishTransformer(transformer);
return;
}
@@ -988,8 +937,8 @@ void Executor::onJobFinished(const qbs::ErrorInfo &err)
ExecutorJob * const job = qobject_cast<ExecutorJob *>(sender());
QBS_CHECK(job);
if (m_evalContext->engine()->isActive()) {
- m_logger.qbsDebug() << "Executor job finished while rule execution is pausing. "
- "Delaying slot execution.";
+ qCDebug(lcExec) << "Executor job finished while rule execution is pausing. "
+ "Delaying slot execution.";
QTimer::singleShot(0, job, [job, err] { job->finished(err); });
return;
}
@@ -1041,12 +990,8 @@ void Executor::checkForUnbuiltProducts()
m_logger.qbsInfo() << Tr::tr("Build done%1.").arg(configString());
} else {
m_error.append(Tr::tr("The following products could not be built%1:").arg(configString()));
- for (const ResolvedProductConstPtr &p : qAsConst(unbuiltProducts)) {
- QString errorMessage = Tr::tr("\t%1").arg(p->name);
- if (p->profile != m_project->profile())
- errorMessage += Tr::tr(" (for profile '%1')").arg(p->profile);
- m_error.append(errorMessage);
- }
+ for (const ResolvedProductConstPtr &p : qAsConst(unbuiltProducts))
+ m_error.append(Tr::tr("\t%1").arg(p->fullDisplayName()));
}
}
@@ -1056,10 +1001,10 @@ bool Executor::checkNodeProduct(BuildGraphNode *node)
return true;
// TODO: Turn this into a warning once we have a reliable C++ scanner.
- m_logger.qbsTrace() << "Ignoring node " << node->toString() << ", which belongs to a "
- "product that is not part of the list of products to build. "
- "Possible reasons are an erroneous project design or a false "
- "positive from a dependency scanner.";
+ qCDebug(lcExec) << "Ignoring node " << node->toString() << ", which belongs to a "
+ "product that is not part of the list of products to build. "
+ "Possible reasons are an erroneous project design or a false "
+ "positive from a dependency scanner.";
finishNode(node);
return false;
}
diff --git a/src/lib/corelib/buildgraph/executor.h b/src/lib/corelib/buildgraph/executor.h
index 5371441a0..849920aee 100644
--- a/src/lib/corelib/buildgraph/executor.h
+++ b/src/lib/corelib/buildgraph/executor.h
@@ -178,8 +178,6 @@ private:
qint64 m_elapsedTimeRules;
qint64 m_elapsedTimeScanners;
qint64 m_elapsedTimeInstalling;
- const bool m_doTrace;
- const bool m_doDebug;
};
} // namespace Internal
diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
index 6b545dd24..89d6bde93 100644
--- a/src/lib/corelib/buildgraph/inputartifactscanner.cpp
+++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
@@ -48,6 +48,7 @@
#include "rulesevaluationcontext.h"
#include <language/language.h>
+#include <logging/categories.h>
#include <tools/fileinfo.h>
#include <tools/scannerpluginmanager.h>
#include <tools/qbsassert.h>
@@ -121,13 +122,8 @@ void InputArtifactScanner::scan()
if (m_artifact->inputsScanned)
return;
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace()
- << QString::fromLatin1("[DEPSCAN] inputs for %1 [%2] in product '%3'")
- .arg(m_artifact->filePath(),
- m_artifact->fileTags().toStringList().join(QLatin1String(", ")),
- m_artifact->product->name);
- }
+ qCDebug(lcDepScan) << "scan inputs for" << m_artifact->filePath() << m_artifact->fileTags()
+ << "in product" << m_artifact->product->name;
m_artifact->inputsScanned = true;
@@ -139,7 +135,7 @@ void InputArtifactScanner::scan()
const Set<Artifact *> childrenAddedByScanner = m_artifact->childrenAddedByScanner;
m_artifact->childrenAddedByScanner.clear();
for (Artifact * const dependency : childrenAddedByScanner)
- disconnect(m_artifact, dependency, m_logger);
+ disconnect(m_artifact, dependency);
for (Artifact * const inputArtifact : qAsConst(m_artifact->transformer->inputs))
scanForFileDependencies(inputArtifact);
@@ -147,12 +143,8 @@ void InputArtifactScanner::scan()
void InputArtifactScanner::scanForFileDependencies(Artifact *inputArtifact)
{
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace()
- << QString::fromLatin1("[DEPSCAN] input artifact %1 [%2]")
- .arg(inputArtifact->filePath(),
- inputArtifact->fileTags().toStringList().join(QLatin1String(", ")));
- }
+ qCDebug(lcDepScan) << "input artifact" << inputArtifact->filePath()
+ << inputArtifact->fileTags();
InputArtifactScannerContext::CacheItem &cacheItem = m_context->cache[inputArtifact->properties];
Set<QString> visitedFilePaths;
@@ -194,7 +186,7 @@ Set<DependencyScanner *> InputArtifactScanner::scannersForArtifact(const Artifac
for (const ResolvedScannerConstPtr &scanner : qAsConst(product->scanners)) {
if (scanner->inputs.contains(fileTag)) {
cache.scanners += DependencyScannerPtr(
- new UserDependencyScanner(scanner, m_logger, engine));
+ new UserDependencyScanner(scanner, engine));
break;
}
}
@@ -210,30 +202,23 @@ void InputArtifactScanner::scanForScannerFileDependencies(DependencyScanner *sca
QList<FileResourceBase *> *filesToScan,
InputArtifactScannerContext::ScannerResolvedDependenciesCache &cache)
{
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << QString::fromLatin1("[DEPSCAN] file %1")
- .arg(fileToBeScanned->filePath());
- }
+ qCDebug(lcDepScan) << "file" << fileToBeScanned->filePath();
const bool cacheHit = cache.valid;
if (!cacheHit) {
cache.valid = true;
cache.searchPaths = scanner->collectSearchPaths(inputArtifact);
}
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace()
- << "[DEPSCAN] include paths (cache " << (cacheHit ? "hit)" : "miss)");
- for (const QString &s : qAsConst(cache.searchPaths))
- m_logger.qbsTrace() << " " << s;
- }
+ qCDebug(lcDepScan) << "include paths (cache" << (cacheHit ? "hit)" : "miss)");
+ for (const QString &s : qAsConst(cache.searchPaths))
+ qCDebug(lcDepScan) << " " << s;
const QString &filePathToBeScanned = fileToBeScanned->filePath();
RawScanResults::ScanData &scanData = m_rawScanResults.findScanData(fileToBeScanned, scanner,
m_artifact->properties);
if (scanData.lastScanTime < fileToBeScanned->timestamp()) {
try {
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "scanning " << FileInfo::fileName(filePathToBeScanned);
+ qCDebug(lcDepScan) << "scanning" << FileInfo::fileName(filePathToBeScanned);
scanWithScannerPlugin(scanner, fileToBeScanned, &scanData.rawScanResult);
scanData.lastScanTime = FileTime::currentTime();
} catch (const ErrorInfo &error) {
@@ -275,8 +260,7 @@ void InputArtifactScanner::resolveScanResultDependencies(const Artifact *inputAr
}
unresolved:
- if (m_logger.traceEnabled())
- m_logger.qbsWarning() << "[DEPSCAN] unresolved dependency " << dependencyFilePath;
+ qCWarning(lcDepScan) << "unresolved dependency " << dependencyFilePath;
continue;
resolved:
@@ -309,8 +293,7 @@ void InputArtifactScanner::handleDependency(ResolvedDependency &dependency)
if (!dependency.file) {
// The dependency is an existing file but does not exist in the build graph.
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[DEPSCAN] add new file dependency " << dependency.filePath;
+ qCDebug(lcDepScan) << "add new file dependency" << dependency.filePath;
fileDependency = new FileDependency();
dependency.file = fileDependency;
@@ -318,23 +301,17 @@ void InputArtifactScanner::handleDependency(ResolvedDependency &dependency)
product->topLevelProject()->buildData->insertFileDependency(fileDependency);
} else if (fileDependency) {
// The dependency exists in the project's list of file dependencies.
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "[DEPSCAN] add existing file dependency "
- << dependency.filePath;
- }
+ qCDebug(lcDepScan) << "add existing file dependency" << dependency.filePath;
} else if (artifactDependency->product == product) {
// The dependency is in our product.
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "[DEPSCAN] add artifact dependency " << dependency.filePath
- << " (from this product)";
- }
+ qCDebug(lcDepScan) << "add artifact dependency" << dependency.filePath <<
+ "(from this product)";
} else {
// The dependency is in some other product.
ResolvedProduct * const otherProduct = artifactDependency->product;
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "[DEPSCAN] add artifact dependency " << dependency.filePath
- << " (from product " << otherProduct->uniqueName() << ')';
- }
+ qCDebug(lcDepScan)
+ << "add artifact dependency" << dependency.filePath
+ << " (from product" << otherProduct->uniqueName() << ')';
}
if (m_artifact == dependency.file)
@@ -347,7 +324,7 @@ void InputArtifactScanner::handleDependency(ResolvedDependency &dependency)
} else {
if (m_artifact->children.contains(artifactDependency))
return;
- safeConnect(m_artifact, artifactDependency, m_logger);
+ safeConnect(m_artifact, artifactDependency);
m_artifact->childrenAddedByScanner += artifactDependency;
m_newDependencyAdded = true;
}
diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.cpp b/src/lib/corelib/buildgraph/jscommandexecutor.cpp
index 7bfda7881..1d256fa2c 100644
--- a/src/lib/corelib/buildgraph/jscommandexecutor.cpp
+++ b/src/lib/corelib/buildgraph/jscommandexecutor.cpp
@@ -46,6 +46,7 @@
#include <language/language.h>
#include <language/preparescriptobserver.h>
+#include <language/resolvedfilecontext.h>
#include <language/scriptengine.h>
#include <logging/logger.h>
#include <tools/codelocation.h>
diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
index 4b435e71f..7d4a41e2f 100644
--- a/src/lib/corelib/buildgraph/processcommandexecutor.cpp
+++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
@@ -45,6 +45,7 @@
#include <language/language.h>
#include <language/scriptengine.h>
+#include <logging/categories.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/commandechomode.h>
@@ -62,7 +63,6 @@
#include <QtCore/qtemporaryfile.h>
#include <QtCore/qtimer.h>
-#include <QtScript/qscriptengine.h>
#include <QtScript/qscriptvalue.h>
namespace qbs {
@@ -81,7 +81,7 @@ void ProcessCommandExecutor::doSetup()
{
const ProcessCommand * const cmd = processCommand();
const QString program = ExecutableFinder(transformer()->product(),
- transformer()->product()->buildEnvironment, logger())
+ transformer()->product()->buildEnvironment)
.findExecutable(cmd->program(), cmd->workingDir());
QProcessEnvironment env = m_buildEnvironment;
@@ -126,11 +126,9 @@ void ProcessCommandExecutor::doStart()
if (!cmd->responseFileUsagePrefix().isEmpty()) {
const int commandLineLength = m_shellInvocation.length();
if (cmd->responseFileThreshold() >= 0 && commandLineLength > cmd->responseFileThreshold()) {
- if (logger().debugEnabled()) {
- logger().qbsDebug() << QString::fromLatin1("[EXEC] Using response file. "
+ qCDebug(lcExec) << QString::fromUtf8("Using response file. "
"Threshold is %1. Command line length %2.")
.arg(cmd->responseFileThreshold()).arg(commandLineLength);
- }
// The QTemporaryFile keeps a handle on the file, even if closed.
// On Windows, some commands (e.g. msvc link.exe) won't accept that.
@@ -167,15 +165,9 @@ void ProcessCommandExecutor::doStart()
}
}
- if (logger().debugEnabled()) {
- logger().qbsDebug() << "[EXEC] Running external process; full command line is: "
- << m_shellInvocation;
- }
+ qCDebug(lcExec) << "Running external process; full command line is:" << m_shellInvocation;
const QProcessEnvironment &additionalVariables = cmd->environment();
- if (logger().traceEnabled()) {
- logger().qbsTrace() << "[EXEC] Additional environment:"
- << additionalVariables.toStringList();
- }
+ qCDebug(lcExec) << "Additional environment:" << additionalVariables.toStringList();
m_process.setWorkingDirectory(workingDir);
m_process.start(m_program, arguments);
}
@@ -301,6 +293,12 @@ void ProcessCommandExecutor::sendProcessOutput()
void ProcessCommandExecutor::onProcessError()
{
+ if (scriptEngine()->isActive()) {
+ qCDebug(lcExec) << "Command error while rule execution is pausing. "
+ "Delaying slot execution.";
+ QTimer::singleShot(0, this, &ProcessCommandExecutor::onProcessError);
+ return;
+ }
switch (m_process.error()) {
case QProcess::FailedToStart: {
removeResponseFile();
@@ -329,6 +327,12 @@ void ProcessCommandExecutor::onProcessError()
void ProcessCommandExecutor::onProcessFinished()
{
+ if (scriptEngine()->isActive()) {
+ qCDebug(lcExec) << "Command finished while rule execution is pausing. "
+ "Delaying slot execution.";
+ QTimer::singleShot(0, this, &ProcessCommandExecutor::onProcessFinished);
+ return;
+ }
removeResponseFile();
sendProcessOutput();
}
diff --git a/src/lib/corelib/buildgraph/projectbuilddata.cpp b/src/lib/corelib/buildgraph/projectbuilddata.cpp
index 25b2651a9..0c9d65cf9 100644
--- a/src/lib/corelib/buildgraph/projectbuilddata.cpp
+++ b/src/lib/corelib/buildgraph/projectbuilddata.cpp
@@ -48,9 +48,11 @@
#include "rulesevaluationcontext.h"
#include "transformer.h"
+#include <language/artifactproperties.h>
#include <language/language.h>
#include <language/preparescriptobserver.h>
#include <language/scriptengine.h>
+#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
@@ -93,13 +95,6 @@ QString ProjectBuildData::deriveBuildGraphFilePath(const QString &buildDir, cons
return buildDir + QLatin1Char('/') + projectId + QLatin1String(".bg");
}
-static QString productNameForErrorMessage(const ResolvedProduct *product)
-{
- return product->profile == product->topLevelProject()->profile()
- && product->multiplexConfigurationId.isEmpty()
- ? product->name : product->uniqueName();
-}
-
void ProjectBuildData::insertIntoLookupTable(FileResourceBase *fileres)
{
QList<FileResourceBase *> &lst
@@ -113,10 +108,10 @@ void ProjectBuildData::insertIntoLookupTable(FileResourceBase *fileres)
error.append(Tr::tr("Conflicting artifacts for file path '%1'.")
.arg(artifact->filePath()));
error.append(Tr::tr("The first artifact comes from product '%1'.")
- .arg(productNameForErrorMessage(otherArtifact->product.get())),
+ .arg(otherArtifact->product->fullDisplayName()),
otherArtifact->product->location);
error.append(Tr::tr("The second artifact comes from product '%1'.")
- .arg(productNameForErrorMessage(artifact->product.get())),
+ .arg(artifact->product->fullDisplayName()),
artifact->product->location);
throw error;
}
@@ -155,24 +150,18 @@ void ProjectBuildData::insertFileDependency(FileDependency *dependency)
insertIntoLookupTable(dependency);
}
-static void disconnectArtifactChildren(Artifact *artifact, const Logger &logger)
+static void disconnectArtifactChildren(Artifact *artifact)
{
- if (logger.traceEnabled()) {
- logger.qbsTrace() << QString::fromLatin1("[BG] disconnectChildren: '%1'")
- .arg(relativeArtifactFileName(artifact));
- }
+ qCDebug(lcBuildGraph) << "disconnect children of" << relativeArtifactFileName(artifact);
for (BuildGraphNode * const child : qAsConst(artifact->children))
child->parents.remove(artifact);
artifact->children.clear();
artifact->childrenAddedByScanner.clear();
}
-static void disconnectArtifactParents(Artifact *artifact, const Logger &logger)
+static void disconnectArtifactParents(Artifact *artifact)
{
- if (logger.traceEnabled()) {
- logger.qbsTrace() << QString::fromLatin1("[BG] disconnectParents: '%1'")
- .arg(relativeArtifactFileName(artifact));
- }
+ qCDebug(lcBuildGraph) << "disconnect parents of" << relativeArtifactFileName(artifact);
for (BuildGraphNode * const parent : qAsConst(artifact->parents)) {
parent->children.remove(artifact);
Artifact *parentArtifact = dynamic_cast<Artifact *>(parent);
@@ -187,10 +176,10 @@ static void disconnectArtifactParents(Artifact *artifact, const Logger &logger)
artifact->parents.clear();
}
-static void disconnectArtifact(Artifact *artifact, const Logger &logger)
+static void disconnectArtifact(Artifact *artifact)
{
- disconnectArtifactChildren(artifact, logger);
- disconnectArtifactParents(artifact, logger);
+ disconnectArtifactChildren(artifact);
+ disconnectArtifactParents(artifact);
}
/*!
@@ -210,7 +199,7 @@ void ProjectBuildData::removeArtifactAndExclusiveDependents(Artifact *artifact,
const NodeSet parentsCopy = artifact->parents;
for (Artifact *parent : filterByType<Artifact>(parentsCopy)) {
bool removeParent = false;
- disconnect(parent, artifact, logger);
+ disconnect(parent, artifact);
if (parent->children.isEmpty()) {
removeParent = true;
} else if (parent->transformer) {
@@ -229,7 +218,7 @@ void ProjectBuildData::removeArtifactAndExclusiveDependents(Artifact *artifact,
removeArtifact(artifact, logger, removeFromDisk, removeFromProduct);
}
-static void removeFromRuleNodes(Artifact *artifact, const Logger &logger)
+static void removeFromRuleNodes(Artifact *artifact)
{
for (const ResolvedProductPtr &product : artifact->product->topLevelProject()->allProducts()) {
if (!product->buildData)
@@ -238,10 +227,8 @@ static void removeFromRuleNodes(Artifact *artifact, const Logger &logger)
if (n->type() != BuildGraphNode::RuleNodeType)
continue;
RuleNode * const ruleNode = static_cast<RuleNode *>(n);
- if (logger.traceEnabled()) {
- logger.qbsTrace() << "[BG] remove old input " << artifact->filePath()
- << " from rule " << ruleNode->rule()->toString();
- }
+ qCDebug(lcBuildGraph) << "remove old input" << artifact->filePath()
+ << "from rule" << ruleNode->rule()->toString();
ruleNode->removeOldInputArtifact(artifact);
}
}
@@ -250,14 +237,12 @@ static void removeFromRuleNodes(Artifact *artifact, const Logger &logger)
void ProjectBuildData::removeArtifact(Artifact *artifact,
const Logger &logger, bool removeFromDisk, bool removeFromProduct)
{
- if (logger.traceEnabled())
- logger.qbsTrace() << "[BG] remove artifact " << relativeArtifactFileName(artifact);
-
+ qCDebug(lcBuildGraph) << "remove artifact" << relativeArtifactFileName(artifact);
if (removeFromDisk)
removeGeneratedArtifactFromDisk(artifact, logger);
removeFromLookupTable(artifact);
- removeFromRuleNodes(artifact, logger);
- disconnectArtifact(artifact, logger);
+ removeFromRuleNodes(artifact);
+ disconnectArtifact(artifact);
if (artifact->transformer) {
artifact->product->unregisterArtifactWithChangedInputs(artifact);
artifact->transformer->outputs.remove(artifact);
@@ -338,25 +323,18 @@ void BuildDataResolver::resolveProductBuildDataForExistingProject(const TopLevel
class CreateRuleNodes : public RuleGraphVisitor
{
public:
- CreateRuleNodes(const ResolvedProductPtr &product, const Logger &logger)
- : m_product(product), m_logger(logger)
+ CreateRuleNodes(const ResolvedProductPtr &product)
+ : m_product(product)
{
}
- const Set<RuleNode *> &leaves() const
- {
- return m_leaves;
- }
-
private:
const ResolvedProductPtr &m_product;
- const Logger &m_logger;
QHash<RuleConstPtr, RuleNode *> m_nodePerRule;
Set<const Rule *> m_rulesOnPath;
QList<const Rule *> m_rulePath;
- Set<RuleNode *> m_leaves;
- void visit(const RuleConstPtr &parentRule, const RuleConstPtr &rule)
+ void visit(const RuleConstPtr &parentRule, const RuleConstPtr &rule) override
{
if (!m_rulesOnPath.insert(rule.get()).second) {
QString pathstr;
@@ -370,27 +348,23 @@ private:
RuleNode *node = m_nodePerRule.value(rule);
if (!node) {
node = new RuleNode;
- m_leaves.insert(node);
m_nodePerRule.insert(rule, node);
node->product = m_product;
node->setRule(rule);
m_product->buildData->nodes += node;
- if (m_logger.debugEnabled()) {
- m_logger.qbsDebug() << "[BG] create " << node->toString()
- << " for product " << m_product->uniqueName();
- }
+ qCDebug(lcBuildGraph) << "create" << node->toString()
+ << "for product" << m_product->uniqueName();
}
if (parentRule) {
RuleNode *parent = m_nodePerRule.value(parentRule);
QBS_CHECK(parent);
- loggedConnect(parent, node, m_logger);
- m_leaves.remove(parent);
+ connect(parent, node);
} else {
m_product->buildData->roots += node;
}
}
- void endVisit(const RuleConstPtr &rule)
+ void endVisit(const RuleConstPtr &rule) override
{
m_rulesOnPath.remove(rule.get());
m_rulePath.removeLast();
@@ -432,7 +406,7 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc
qbsFileArtifact->artifactType = Artifact::SourceFile;
qbsFileArtifact->setFilePath(product->location.filePath());
qbsFileArtifact->properties = product->moduleProperties;
- insertArtifact(product, qbsFileArtifact, m_logger);
+ insertArtifact(product, qbsFileArtifact);
}
qbsFileArtifact->addFileTag("qbs");
artifactsPerFileTag["qbs"].insert(qbsFileArtifact);
@@ -443,14 +417,14 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc
if (lookupArtifact(product, filePath))
continue; // ignore duplicate artifacts
- Artifact *artifact = createArtifact(product, sourceArtifact, m_logger);
+ Artifact *artifact = createArtifact(product, sourceArtifact);
for (const FileTag &fileTag : artifact->fileTags())
artifactsPerFileTag[fileTag].insert(artifact);
}
RuleGraph ruleGraph;
ruleGraph.build(product->rules, product->fileTags);
- CreateRuleNodes crn(product, m_logger);
+ CreateRuleNodes crn(product);
ruleGraph.accept(&crn);
connectRulesToDependencies(product, product->dependencies);
@@ -479,7 +453,7 @@ void BuildDataResolver::connectRulesToDependencies(const ResolvedProductPtr &pro
if (areRulesCompatible(ruleNode, depRuleNode)
|| (ruleNode->rule()->inputsFromDependencies.contains(installableTag)
&& isRootRuleNode(depRuleNode))) {
- loggedConnect(ruleNode, depRuleNode, m_logger);
+ connect(ruleNode, depRuleNode);
}
}
}
diff --git a/src/lib/corelib/buildgraph/qtmocscanner.cpp b/src/lib/corelib/buildgraph/qtmocscanner.cpp
index bc3df54f5..3251ff167 100644
--- a/src/lib/corelib/buildgraph/qtmocscanner.cpp
+++ b/src/lib/corelib/buildgraph/qtmocscanner.cpp
@@ -45,6 +45,7 @@
#include "projectbuilddata.h"
#include "rawscanresults.h"
#include <language/scriptengine.h>
+#include <logging/categories.h>
#include <logging/translator.h>
#include <plugins/scanner/scanner.h>
#include <tools/fileinfo.h>
@@ -79,13 +80,13 @@ public:
: m_id(QLatin1String("qt") + actualScanner.id()) {}
private:
- QStringList collectSearchPaths(Artifact *) { return QStringList(); }
- QStringList collectDependencies(FileResourceBase *, const char *) { return QStringList(); }
- bool recursive() const { return false; }
- const void *key() const { return nullptr; }
- QString createId() const { return m_id; }
+ QStringList collectSearchPaths(Artifact *) override { return QStringList(); }
+ QStringList collectDependencies(FileResourceBase *, const char *) override { return QStringList(); }
+ bool recursive() const override { return false; }
+ const void *key() const override { return nullptr; }
+ QString createId() const override { return m_id; }
bool areModulePropertiesCompatible(const PropertyMapConstPtr &,
- const PropertyMapConstPtr &) const
+ const PropertyMapConstPtr &) const override
{
return true;
}
@@ -93,12 +94,10 @@ private:
const QString m_id;
};
-QtMocScanner::QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue,
- const Logger &logger)
+QtMocScanner::QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue)
: m_tags(*commonFileTags())
, m_product(product)
, m_targetScriptValue(targetScriptValue)
- , m_logger(logger)
, m_cppScanner(0)
, m_hppScanner(0)
{
@@ -177,8 +176,7 @@ void QtMocScanner::findIncludedMocCppFiles()
if (!m_includedMocCppFiles.isEmpty())
return;
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[QtMocScanner] looking for included moc_XXX.cpp files";
+ qCDebug(lcMocScan) << "looking for included moc_XXX.cpp files";
static const FileTags mocCppTags = {m_tags.cpp, m_tags.objcpp};
for (Artifact *artifact : m_product->lookupArtifactsByFileTags(mocCppTags)) {
@@ -188,9 +186,7 @@ void QtMocScanner::findIncludedMocCppFiles()
QString includedFileName = dependency.fileName();
if (includedFileName.startsWith(QLatin1String("moc_"))
&& includedFileName.endsWith(QLatin1String(".cpp"))) {
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[QtMocScanner] " << artifact->fileName()
- << " includes " << includedFileName;
+ qCDebug(lcMocScan) << artifact->fileName() << "includes" << includedFileName;
includedFileName.remove(0, 4);
includedFileName.chop(4);
m_includedMocCppFiles.insert(includedFileName, artifact->fileName());
@@ -233,8 +229,7 @@ QScriptValue QtMocScanner::apply(QScriptEngine *engine, const Artifact *artifact
findIncludedMocCppFiles();
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[QtMocScanner] scanning " << artifact->toString();
+ qCDebug(lcMocScan) << "scanning" << artifact->toString();
bool hasQObjectMacro = false;
bool mustCompile = false;
@@ -264,11 +259,9 @@ QScriptValue QtMocScanner::apply(QScriptEngine *engine, const Artifact *artifact
}
}
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "[QtMocScanner] hasQObjectMacro: " << hasQObjectMacro
- << " mustCompile: " << mustCompile
- << " hasPluginMetaDataMacro: " << hasPluginMetaDataMacro;
- }
+ qCDebug(lcMocScan) << "hasQObjectMacro:" << hasQObjectMacro
+ << "mustCompile:" << mustCompile
+ << "hasPluginMetaDataMacro:" << hasPluginMetaDataMacro;
QScriptValue obj = engine->newObject();
obj.setProperty(QLatin1String("hasQObjectMacro"), hasQObjectMacro);
diff --git a/src/lib/corelib/buildgraph/qtmocscanner.h b/src/lib/corelib/buildgraph/qtmocscanner.h
index 67ef03fec..9b0a00f31 100644
--- a/src/lib/corelib/buildgraph/qtmocscanner.h
+++ b/src/lib/corelib/buildgraph/qtmocscanner.h
@@ -41,7 +41,6 @@
#define QBS_QTMOCSCANNER_H
#include <language/language.h>
-#include <logging/logger.h>
#include <QtCore/qhash.h>
#include <QtCore/qstring.h>
@@ -63,8 +62,7 @@ struct CommonFileTags;
class QtMocScanner
{
public:
- explicit QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue,
- const Logger &logger);
+ explicit QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue);
~QtMocScanner();
private:
@@ -76,7 +74,6 @@ private:
const CommonFileTags &m_tags;
const ResolvedProductPtr &m_product;
QScriptValue m_targetScriptValue;
- const Logger &m_logger;
QHash<QString, QString> m_includedMocCppFiles;
ScannerPlugin *m_cppScanner;
ScannerPlugin *m_objcppScanner;
diff --git a/src/lib/corelib/buildgraph/rawscanresults.cpp b/src/lib/corelib/buildgraph/rawscanresults.cpp
index 616adc964..7285302db 100644
--- a/src/lib/corelib/buildgraph/rawscanresults.cpp
+++ b/src/lib/corelib/buildgraph/rawscanresults.cpp
@@ -85,8 +85,7 @@ RawScanResults::ScanData &RawScanResults::findScanData(
{
std::vector<ScanData> &scanDataForFile = m_rawScanData[file->filePath()];
const QString &scannerId = scanner->id();
- for (auto it = scanDataForFile.begin(); it != scanDataForFile.end(); ++it) {
- ScanData &scanData = *it;
+ for (auto &scanData : scanDataForFile) {
if (scannerId != scanData.scannerId)
continue;
if (!scanner->areModulePropertiesCompatible(moduleProperties, scanData.moduleProperties))
diff --git a/src/lib/corelib/buildgraph/rescuableartifactdata.h b/src/lib/corelib/buildgraph/rescuableartifactdata.h
index 92718ae4b..d1d9ae12b 100644
--- a/src/lib/corelib/buildgraph/rescuableartifactdata.h
+++ b/src/lib/corelib/buildgraph/rescuableartifactdata.h
@@ -62,6 +62,8 @@ public:
void load(PersistentPool &pool);
void store(PersistentPool &pool) const;
+ bool isValid() const { return !!properties; }
+
struct ChildData
{
ChildData(const QString &n = QString(), const QString &m = QString(),
diff --git a/src/lib/corelib/buildgraph/rulegraph.cpp b/src/lib/corelib/buildgraph/rulegraph.cpp
index 8bd42c9f9..2aa9f6a77 100644
--- a/src/lib/corelib/buildgraph/rulegraph.cpp
+++ b/src/lib/corelib/buildgraph/rulegraph.cpp
@@ -50,7 +50,7 @@ RuleGraph::RuleGraph()
{
}
-void RuleGraph::build(const Set<RulePtr> &rules, const FileTags &productFileTags)
+void RuleGraph::build(const QList<RulePtr> &rules, const FileTags &productFileTags)
{
QMap<FileTag, QList<const Rule *> > inputFileTagToRule;
m_rules.reserve(rules.size());
diff --git a/src/lib/corelib/buildgraph/rulegraph.h b/src/lib/corelib/buildgraph/rulegraph.h
index 7338ae746..75302ce36 100644
--- a/src/lib/corelib/buildgraph/rulegraph.h
+++ b/src/lib/corelib/buildgraph/rulegraph.h
@@ -65,7 +65,7 @@ class RuleGraph
public:
RuleGraph();
- void build(const Set<RulePtr> &rules, const FileTags &productFileTag);
+ void build(const QList<RulePtr> &rules, const FileTags &productFileTag);
void accept(RuleGraphVisitor *visitor) const;
void dump() const;
diff --git a/src/lib/corelib/buildgraph/rulenode.cpp b/src/lib/corelib/buildgraph/rulenode.cpp
index d4558787d..888f2b28f 100644
--- a/src/lib/corelib/buildgraph/rulenode.cpp
+++ b/src/lib/corelib/buildgraph/rulenode.cpp
@@ -47,6 +47,7 @@
#include "transformer.h"
#include <language/language.h>
+#include <logging/categories.h>
#include <logging/logger.h>
#include <tools/persistence.h>
#include <tools/qbsassert.h>
@@ -85,24 +86,21 @@ void RuleNode::apply(const Logger &logger, const ArtifactSet &changedInputs,
result->upToDate = changedInputs.isEmpty() && addedInputs.isEmpty() && removedInputs.isEmpty()
&& m_rule->declaresInputs() && m_rule->requiresInputs;
- if (logger.traceEnabled()) {
- logger.qbsTrace()
- << "[BG] consider " << (m_rule->isDynamic() ? "dynamic " : "")
- << (m_rule->multiplex ? "multiplex " : "")
- << "rule node " << m_rule->toString()
- << "\n\tchanged: " << changedInputs.toString()
- << "\n\tcompatible: " << allCompatibleInputs.toString()
- << "\n\tadded: " << addedInputs.toString()
- << "\n\tremoved: " << removedInputs.toString();
- }
+ qCDebug(lcBuildGraph).noquote().nospace()
+ << "consider " << (m_rule->isDynamic() ? "dynamic " : "")
+ << (m_rule->multiplex ? "multiplex " : "")
+ << "rule node " << m_rule->toString()
+ << "\n\tchanged: " << changedInputs.toString()
+ << "\n\tcompatible: " << allCompatibleInputs.toString()
+ << "\n\tadded: " << addedInputs.toString()
+ << "\n\tremoved: " << removedInputs.toString();
ArtifactSet inputs = changedInputs;
if (product->isMarkedForReapplication(m_rule)) {
QBS_CHECK(m_rule->multiplex);
result->upToDate = false;
product->unmarkForReapplication(m_rule);
- if (logger.traceEnabled())
- logger.qbsTrace() << "[BG] rule is marked for reapplication " << m_rule->toString();
+ qCDebug(lcBuildGraph) << "rule is marked for reapplication " << m_rule->toString();
}
if (m_rule->multiplex)
diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp
index 0df7d204d..dec5cd051 100644
--- a/src/lib/corelib/buildgraph/rulesapplicator.cpp
+++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp
@@ -52,7 +52,9 @@
#include <language/language.h>
#include <language/preparescriptobserver.h>
#include <language/propertymapinternal.h>
+#include <language/resolvedfilecontext.h>
#include <language/scriptengine.h>
+#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
@@ -95,7 +97,7 @@ void RulesApplicator::applyRule(const RuleConstPtr &rule, const ArtifactSet &inp
m_completeInputSet = inputArtifacts;
if (rule->name == QLatin1String("QtCoreMocRule")) {
delete m_mocScanner;
- m_mocScanner = new QtMocScanner(m_product, scope(), m_logger);
+ m_mocScanner = new QtMocScanner(m_product, scope());
}
QScriptValue prepareScriptContext = engine()->newObject();
prepareScriptContext.setPrototype(engine()->globalObject());
@@ -120,10 +122,8 @@ void RulesApplicator::handleRemovedRuleOutputs(const ArtifactSet &inputArtifacts
ArtifactSet artifactsToRemove;
const TopLevelProject *project = 0;
for (Artifact * const removedArtifact : outputArtifactsToRemove) {
- if (logger.traceEnabled()) {
- logger.qbsTrace() << "[BG] dynamic rule removed output artifact "
- << removedArtifact->toString();
- }
+ qCDebug(lcBuildGraph) << "dynamic rule removed output artifact"
+ << removedArtifact->toString();
if (!project)
project = removedArtifact->product->topLevelProject();
project->buildData->removeArtifactAndExclusiveDependents(removedArtifact, logger, true,
@@ -161,11 +161,8 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QScriptValue &p
{
evalContext()->checkForCancelation();
- if (m_logger.debugEnabled()) {
- m_logger.qbsDebug() << QString::fromLatin1("[BG] apply rule ") << m_rule->toString()
- << QString::fromLatin1(" ")
- << toStringList(inputArtifacts).join(QLatin1String(",\n "));
- }
+ qCDebug(lcBuildGraph) << "apply rule" << m_rule->toString()
+ << toStringList(inputArtifacts).join(QLatin1String(",\n "));
QList<std::pair<const RuleArtifact *, Artifact *> > ruleArtifactArtifactMap;
QList<Artifact *> outputArtifacts;
@@ -209,7 +206,7 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QScriptValue &p
for (Artifact * const outputArtifact : qAsConst(outputArtifacts)) {
for (Artifact * const dependency : qAsConst(m_transformer->explicitlyDependsOn))
- loggedConnect(outputArtifact, dependency, m_logger);
+ connect(outputArtifact, dependency);
outputArtifact->product->unregisterArtifactWithChangedInputs(outputArtifact);
}
@@ -363,24 +360,25 @@ Artifact *RulesApplicator::createOutputArtifact(const QString &filePath, const F
QScopedPointer<Artifact> newArtifact(new Artifact);
newArtifact->artifactType = Artifact::Generated;
newArtifact->setFilePath(outputPath);
- insertArtifact(m_product, newArtifact.data(), m_logger);
+ insertArtifact(m_product, newArtifact.data());
m_createdArtifacts += newArtifact.data();
outputArtifact = newArtifact.take();
}
- outputArtifact->setFileTags(
- fileTags.isEmpty() ? m_product->fileTagsForFileName(outputArtifact->fileName())
- : fileTags);
outputArtifact->alwaysUpdated = alwaysUpdated;
outputArtifact->properties = m_product->moduleProperties;
+ FileTags outputArtifactFileTags = fileTags.isEmpty()
+ ? m_product->fileTagsForFileName(outputArtifact->fileName()) : fileTags;
for (int i = 0; i < m_product->artifactProperties.count(); ++i) {
const ArtifactPropertiesConstPtr &props = m_product->artifactProperties.at(i);
- if (outputArtifact->fileTags().intersects(props->fileTagsFilter())) {
+ if (outputArtifactFileTags.intersects(props->fileTagsFilter())) {
outputArtifact->properties = props->propertyMap();
+ outputArtifactFileTags += props->extraFileTags();
break;
}
}
+ outputArtifact->setFileTags(outputArtifactFileTags);
// Let a positive value of qbs.install imply the file tag "installable".
if (outputArtifact->properties->qbsPropertyValue(QLatin1String("install")).toBool())
@@ -388,7 +386,7 @@ Artifact *RulesApplicator::createOutputArtifact(const QString &filePath, const F
for (Artifact * const inputArtifact : inputArtifacts) {
QBS_CHECK(outputArtifact != inputArtifact);
- loggedConnect(outputArtifact, inputArtifact, m_logger);
+ connect(outputArtifact, inputArtifact);
}
outputArtifact->transformer = m_transformer;
@@ -505,7 +503,7 @@ Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const QScriptValu
obj.property(QLatin1String("explicitlyDependsOn")).toVariant().toStringList());
for (const FileTag &tag : explicitlyDependsOn) {
for (Artifact * const dependency : m_product->lookupArtifactsByFileTag(tag))
- loggedConnect(output, dependency, m_logger);
+ connect(output, dependency);
}
ArtifactBindingsExtractor().apply(output, obj);
return output;
diff --git a/src/lib/corelib/buildgraph/timestampsupdater.cpp b/src/lib/corelib/buildgraph/timestampsupdater.cpp
index 2e2e8a6c6..62e395777 100644
--- a/src/lib/corelib/buildgraph/timestampsupdater.cpp
+++ b/src/lib/corelib/buildgraph/timestampsupdater.cpp
@@ -73,7 +73,7 @@ public:
}
private:
- void doVisit(Artifact *artifact)
+ void doVisit(Artifact *artifact) override
{
if (FileInfo(artifact->filePath()).exists())
artifact->setTimestamp(m_now);
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 439365d33..570eb4605 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -294,6 +294,8 @@ QbsLibrary {
name: "logging"
prefix: name + '/'
files: [
+ "categories.cpp",
+ "categories.h",
"ilogsink.cpp",
"logger.cpp",
"logger.h",
@@ -354,6 +356,7 @@ QbsLibrary {
"hostosinfo.h",
"id.cpp",
"id.h",
+ "iosutils.h",
"jsliterals.cpp",
"jsliterals.h",
"installoptions.cpp",
@@ -400,6 +403,7 @@ QbsLibrary {
"shellutils.cpp",
"shellutils.h",
"stlutils.h",
+ "stringutils.h",
"toolchains.cpp",
"version.cpp",
"visualstudioversioninfo.cpp",
diff --git a/src/lib/corelib/generators/generatordata.cpp b/src/lib/corelib/generators/generatordata.cpp
index 1ff186580..14f0a9f5f 100644
--- a/src/lib/corelib/generators/generatordata.cpp
+++ b/src/lib/corelib/generators/generatordata.cpp
@@ -47,65 +47,44 @@ namespace qbs {
QString GeneratableProductData::name() const
{
- QString name;
- QMapIterator<QString, ProductData> it(data);
- while (it.hasNext()) {
- it.next();
- QString oldName = name;
- name = it.value().name();
- if (!oldName.isEmpty() && oldName != name)
- throw ErrorInfo(QLatin1String("Products with different names per-configuration "
- "are not compatible with this generator. Consider "
- "using the targetName property instead."));
- }
- return name;
+ return uniqueValue<QString>(&ProductData::name,
+ QLatin1String("Products with different names per configuration are not "
+ "compatible with this generator. "
+ "Consider using the targetName property instead."));
}
CodeLocation GeneratableProductData::location() const
{
- CodeLocation location;
- QMapIterator<QString, ProductData> it(data);
- while (it.hasNext()) {
- it.next();
- CodeLocation oldLocation = location;
- location = it.value().location();
- if (oldLocation.isValid() && oldLocation != location)
- throw ErrorInfo(QLatin1String("Products with different code locations "
- "per-configuration are not compatible with this "
- "generator."));
- }
- return location;
+ return uniqueValue<CodeLocation>(&ProductData::location,
+ QLatin1String("GeneratableProductData::location: internal bug; this should not happen."));
}
QStringList GeneratableProductData::dependencies() const
{
- QStringList list;
- QMapIterator<QString, ProductData> it(data);
- while (it.hasNext()) {
- it.next();
- QStringList oldList = list;
- list = it.value().dependencies();
- if (!oldList.isEmpty() && oldList != list)
- throw ErrorInfo(QLatin1String("Products with different dependency lists "
- "per-configuration are not compatible with this "
- "generator."));
- }
- return list;
+ return uniqueValue<QStringList>(&ProductData::dependencies,
+ QLatin1String("Products with different dependency lists per configuration are not "
+ "compatible with this generator."));
+}
+
+QStringList GeneratableProductData::type() const
+{
+ return uniqueValue<QStringList>(&ProductData::type,
+ QLatin1String("Products with different types per configuration are not "
+ "compatible with this generator."));
+}
+
+QString GeneratableProductData::buildDirectory() const
+{
+ return uniqueValue<QString>(&ProductData::buildDirectory,
+ QLatin1String("GeneratableProductData::buildDirectory: "
+ "internal bug; this should not happen."));
}
QString GeneratableProjectData::name() const
{
- QString name;
- QMapIterator<QString, ProjectData> it(data);
- while (it.hasNext()) {
- it.next();
- QString oldName = name;
- name = it.value().name();
- if (!oldName.isEmpty() && oldName != name)
- throw ErrorInfo(QLatin1String("Projects with different names per-configuration "
- "are not compatible with this generator."));
- }
- return name;
+ return uniqueValue<QString>(&ProjectData::name,
+ QLatin1String("Projects with different names per configuration are not "
+ "compatible with this generator."));
}
CodeLocation GeneratableProjectData::location() const
@@ -118,7 +97,7 @@ CodeLocation GeneratableProjectData::location() const
location = it.value().location();
if (oldLocation.isValid() && oldLocation != location)
throw ErrorInfo(QLatin1String("Projects with different code locations "
- "per-configuration are not compatible with this "
+ "per configuration are not compatible with this "
"generator."));
}
return location;
diff --git a/src/lib/corelib/generators/generatordata.h b/src/lib/corelib/generators/generatordata.h
index 47f4acb93..21e8a1ab4 100644
--- a/src/lib/corelib/generators/generatordata.h
+++ b/src/lib/corelib/generators/generatordata.h
@@ -44,22 +44,78 @@
#include <QtCore/qmap.h>
#include <api/project.h>
#include <api/projectdata.h>
+#include <tools/error.h>
#include <tools/installoptions.h>
+#include <tools/set.h>
+#include <functional>
namespace qbs {
typedef QMap<QString, Project> GeneratableProjectMap;
-typedef QMap<QString, ProjectData> GeneratableProjectDataMap;
-typedef QMap<QString, ProductData> GeneratableProductDataMap;
-struct QBS_EXPORT GeneratableProductData {
- GeneratableProductDataMap data;
+template <typename U> struct IMultiplexableContainer {
+ QMap<QString, U> data;
+
+ template <typename T> T uniqueValue(const std::function<T(const U &data)> &func,
+ const QString &errorMessage) const
+ {
+ auto it = data.begin(), end = data.end();
+ auto value = func(*it++);
+ for (; it != end; ++it) {
+ if (value != func(*it))
+ throw ErrorInfo(errorMessage);
+ }
+ return value;
+ }
+
+ void forEach(const std::function<void(const QString &configurationName,
+ const U &data)> &func) const
+ {
+ QMapIterator<QString, U> it(data);
+ while (it.hasNext()) {
+ it.next();
+ func(it.key(), it.value());
+ }
+ }
+
+ void forEach(const std::function<void(const std::string &configurationName,
+ const U &data)> &func) const
+ {
+ QMapIterator<QString, U> it(data);
+ while (it.hasNext()) {
+ it.next();
+ func(it.key().toStdString(), it.value());
+ }
+ }
+
+ const U operator[](const QString &configurationName)
+ {
+ return data[configurationName];
+ }
+
+ const U operator[](const std::string &configurationName)
+ {
+ return data[QString::fromStdString(configurationName)];
+ }
+
+ bool isValid() const
+ {
+ return !data.isEmpty();
+ }
+
+protected:
+ IMultiplexableContainer() { }
+};
+
+struct QBS_EXPORT GeneratableProductData : public IMultiplexableContainer<ProductData> {
QString name() const;
CodeLocation location() const;
QStringList dependencies() const;
+ QStringList type() const;
+ QString buildDirectory() const;
};
-struct QBS_EXPORT GeneratableProjectData {
+struct QBS_EXPORT GeneratableProjectData : public IMultiplexableContainer<ProjectData> {
struct Id {
private:
friend struct GeneratableProjectData;
@@ -70,7 +126,6 @@ struct QBS_EXPORT GeneratableProjectData {
bool operator<(const Id &id) const { return value < id.value; }
};
- GeneratableProjectDataMap data;
QList<GeneratableProjectData> subProjects;
QList<GeneratableProductData> products;
QString name() const;
@@ -87,6 +142,51 @@ struct QBS_EXPORT GeneratableProject : public GeneratableProjectData {
QFileInfo filePath() const;
bool hasMultipleConfigurations() const;
QStringList commandLine() const;
+
+ void forEach(const std::function<void(const QString &configurationName,
+ const Project &data)> &func) const
+ {
+ QMapIterator<QString, Project> it(projects);
+ while (it.hasNext()) {
+ it.next();
+ func(it.key(), it.value());
+ }
+ }
+
+ void forEach(const std::function<void(const std::string &configurationName,
+ const Project &data)> &func) const
+ {
+ QMapIterator<QString, Project> it(projects);
+ while (it.hasNext()) {
+ it.next();
+ func(it.key().toStdString(), it.value());
+ }
+ }
+
+ const Project operator[](const QString &configurationName) const
+ {
+ return projects[configurationName];
+ }
+
+ const Project operator[](const std::string &configurationName) const
+ {
+ return projects[QString::fromStdString(configurationName)];
+ }
+
+ bool isValid() const
+ {
+ return !data.isEmpty() && !projects.isEmpty();
+ }
+
+ const ProjectData projectData(const QString &configurationName) const
+ {
+ return data[configurationName];
+ }
+
+ const ProjectData projectData(const std::string &configurationName) const
+ {
+ return data[QString::fromStdString(configurationName)];
+ }
};
} // namespace qbs
diff --git a/src/lib/corelib/jsextensions/environmentextension.cpp b/src/lib/corelib/jsextensions/environmentextension.cpp
index 06e5e0765..39a57a17b 100644
--- a/src/lib/corelib/jsextensions/environmentextension.cpp
+++ b/src/lib/corelib/jsextensions/environmentextension.cpp
@@ -112,12 +112,6 @@ QScriptValue EnvironmentExtension::js_getEnv(QScriptContext *context, QScriptEng
const QString name = context->argument(0).toString();
const QString value = procenv->value(name);
-
- // Don't track environment variable access inside environment setup scripts
- // since change tracking is irrelevant for them
- if (procenv == &env)
- static_cast<ScriptEngine *>(engine)->addEnvironmentVariable(name, value);
-
return value.isNull() ? engine->undefinedValue() : value;
}
diff --git a/src/lib/corelib/jsextensions/fileinfoextension.cpp b/src/lib/corelib/jsextensions/fileinfoextension.cpp
index 74362dc4d..8b951ddbf 100644
--- a/src/lib/corelib/jsextensions/fileinfoextension.cpp
+++ b/src/lib/corelib/jsextensions/fileinfoextension.cpp
@@ -62,6 +62,7 @@ public:
static QScriptValue js_path(QScriptContext *context, QScriptEngine *engine);
static QScriptValue js_fileName(QScriptContext *context, QScriptEngine *engine);
static QScriptValue js_baseName(QScriptContext *context, QScriptEngine *engine);
+ static QScriptValue js_cleanPath(QScriptContext *context, QScriptEngine *engine);
static QScriptValue js_completeBaseName(QScriptContext *context, QScriptEngine *engine);
static QScriptValue js_relativePath(QScriptContext *context, QScriptEngine *engine);
static QScriptValue js_resolvePath(QScriptContext *context, QScriptEngine *engine);
@@ -84,6 +85,8 @@ static void initializeJsExtensionFileInfo(QScriptValue extensionObject)
engine->newFunction(FileInfoExtension::js_fileName));
fileInfoObj.setProperty(QLatin1String("baseName"),
engine->newFunction(FileInfoExtension::js_baseName));
+ fileInfoObj.setProperty(QLatin1String("cleanPath"),
+ engine->newFunction(FileInfoExtension::js_cleanPath));
fileInfoObj.setProperty(QLatin1String("completeBaseName"),
engine->newFunction(FileInfoExtension::js_completeBaseName));
fileInfoObj.setProperty(QLatin1String("relativePath"),
@@ -148,6 +151,16 @@ QScriptValue FileInfoExtension::js_baseName(QScriptContext *context, QScriptEngi
return FileInfo::baseName(context->argument(0).toString());
}
+QScriptValue FileInfoExtension::js_cleanPath(QScriptContext *context, QScriptEngine *engine)
+{
+ Q_UNUSED(engine);
+ if (Q_UNLIKELY(context->argumentCount() < 1)) {
+ return context->throwError(QScriptContext::SyntaxError,
+ Tr::tr("cleanPath expects 1 argument"));
+ }
+ return QDir::cleanPath(context->argument(0).toString());
+}
+
QScriptValue FileInfoExtension::js_completeBaseName(QScriptContext *context, QScriptEngine *engine)
{
Q_UNUSED(engine);
diff --git a/src/lib/corelib/jsextensions/process.cpp b/src/lib/corelib/jsextensions/process.cpp
index 962eb0456..cf9c8d645 100644
--- a/src/lib/corelib/jsextensions/process.cpp
+++ b/src/lib/corelib/jsextensions/process.cpp
@@ -40,7 +40,6 @@
#include "jsextensions_p.h"
#include <language/scriptengine.h>
-#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/executablefinder.h>
#include <tools/hostosinfo.h>
@@ -96,7 +95,6 @@ public:
static QScriptValue js_shellQuote(QScriptContext *context, QScriptEngine *engine);
private:
- Logger logger() const;
QString findExecutable(const QString &filePath) const;
QProcess *m_qProcess;
@@ -290,15 +288,9 @@ int Process::exitCode() const
return m_qProcess->exitCode();
}
-Logger Process::logger() const
-{
- ScriptEngine *scriptEngine = static_cast<ScriptEngine *>(engine());
- return scriptEngine->logger();
-}
-
QString Process::findExecutable(const QString &filePath) const
{
- ExecutableFinder exeFinder(ResolvedProductPtr(), m_environment, logger());
+ ExecutableFinder exeFinder(ResolvedProductPtr(), m_environment);
return exeFinder.findExecutable(filePath, m_workingDirectory);
}
diff --git a/src/lib/corelib/jsextensions/textfile.cpp b/src/lib/corelib/jsextensions/textfile.cpp
index d6ea6afaf..bfa2b12d2 100644
--- a/src/lib/corelib/jsextensions/textfile.cpp
+++ b/src/lib/corelib/jsextensions/textfile.cpp
@@ -44,6 +44,7 @@
#include <tools/hostosinfo.h>
#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
#include <QtCore/qobject.h>
#include <QtCore/qtextstream.h>
#include <QtCore/qvariant.h>
@@ -55,17 +56,24 @@
namespace qbs {
namespace Internal {
-class TextFile : public QObject, public QScriptable
+class TextFile : public QObject, public QScriptable, public ResourceAcquiringScriptObject
{
Q_OBJECT
Q_ENUMS(OpenMode)
public:
- enum OpenMode { ReadOnly, WriteOnly, ReadWrite };
+ enum OpenMode
+ {
+ ReadOnly = 1,
+ WriteOnly = 2,
+ ReadWrite = ReadOnly | WriteOnly,
+ Append = 4
+ };
static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine);
~TextFile();
Q_INVOKABLE void close();
+ Q_INVOKABLE QString filePath();
Q_INVOKABLE void setCodec(const QString &codec);
Q_INVOKABLE QString readLine();
Q_INVOKABLE QString readAll();
@@ -80,6 +88,9 @@ private:
bool checkForClosed() const;
+ // ResourceAcquiringScriptObject implementation
+ void releaseResources() override;
+
QFile *m_file;
QTextStream *m_stream;
};
@@ -121,12 +132,13 @@ QScriptValue TextFile::ctor(QScriptContext *context, QScriptEngine *engine)
}
ScriptEngine * const se = static_cast<ScriptEngine *>(engine);
+ se->addResourceAcquiringScriptObject(t);
const DubiousContextList dubiousContexts({
DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving)
});
se->checkContext(QLatin1String("qbs.TextFile"), dubiousContexts);
- return engine->newQObject(t, QScriptEngine::ScriptOwnership);
+ return engine->newQObject(t, QScriptEngine::QtOwnership);
}
TextFile::~TextFile()
@@ -143,19 +155,13 @@ TextFile::TextFile(QScriptContext *context, const QString &filePath, OpenMode mo
m_file = new QFile(filePath);
m_stream = new QTextStream(m_file);
- QIODevice::OpenMode m;
- switch (mode) {
- case ReadWrite:
- m = QIODevice::ReadWrite;
- break;
- case ReadOnly:
- m = QIODevice::ReadOnly;
- break;
- case WriteOnly:
- m = QIODevice::WriteOnly;
- break;
- }
-
+ QIODevice::OpenMode m = QIODevice::NotOpen;
+ if (mode & ReadOnly)
+ m |= QIODevice::ReadOnly;
+ if (mode & WriteOnly)
+ m |= QIODevice::WriteOnly;
+ if (mode & Append)
+ m |= QIODevice::Append;
if (Q_UNLIKELY(!m_file->open(m))) {
context->throwError(Tr::tr("Unable to open file '%1': %2")
.arg(filePath, m_file->errorString()));
@@ -175,6 +181,13 @@ void TextFile::close()
m_file = 0;
}
+QString TextFile::filePath()
+{
+ if (checkForClosed())
+ return QString();
+ return QFileInfo(*m_file).absoluteFilePath();
+}
+
void TextFile::setCodec(const QString &codec)
{
if (checkForClosed())
@@ -232,10 +245,18 @@ bool TextFile::checkForClosed() const
{
if (m_file)
return false;
- context()->throwError(Tr::tr("Access to TextFile object that was already closed."));
+ QScriptContext *ctx = context();
+ if (ctx)
+ ctx->throwError(Tr::tr("Access to TextFile object that was already closed."));
return true;
}
+void TextFile::releaseResources()
+{
+ close();
+ deleteLater();
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/jsextensions/utilitiesextension.cpp b/src/lib/corelib/jsextensions/utilitiesextension.cpp
index a8e5e7c1e..eb17a7175 100644
--- a/src/lib/corelib/jsextensions/utilitiesextension.cpp
+++ b/src/lib/corelib/jsextensions/utilitiesextension.cpp
@@ -449,11 +449,23 @@ QScriptValue UtilitiesExtension::js_msvcCompilerInfo(QScriptContext *context, QS
return context->throwError(QScriptContext::UnknownError,
QLatin1String("msvcCompilerInfo is not available on this platform"));
#else
- if (Q_UNLIKELY(context->argumentCount() != 1))
+ if (Q_UNLIKELY(context->argumentCount() < 1))
return context->throwError(QScriptContext::SyntaxError,
- QLatin1String("msvcCompilerInfo expects 1 argument"));
+ QLatin1String("msvcCompilerInfo expects at least 1 argument"));
const QString compilerFilePath = context->argument(0).toString();
+ const QString compilerLanguage = context->argumentCount() > 1
+ ? context->argument(1).toString()
+ : QString();
+ MSVC::CompilerLanguage language;
+ if (compilerLanguage == QStringLiteral("c"))
+ language = MSVC::CLanguage;
+ else if (compilerLanguage == QStringLiteral("cpp"))
+ language = MSVC::CPlusPlusLanguage;
+ else
+ return context->throwError(QScriptContext::TypeError,
+ QStringLiteral("msvcCompilerInfo expects \"c\" or \"cpp\" as its second argument"));
+
MSVC msvc(compilerFilePath);
VsEnvironmentDetector envdetector;
if (!envdetector.start(&msvc))
@@ -468,7 +480,7 @@ QScriptValue UtilitiesExtension::js_msvcCompilerInfo(QScriptContext *context, QS
return engine->toScriptValue(QVariantMap {
{QStringLiteral("buildEnvironment"), envMap},
- {QStringLiteral("macros"), msvc.compilerDefines(compilerFilePath)},
+ {QStringLiteral("macros"), msvc.compilerDefines(compilerFilePath, language)},
});
} catch (const qbs::ErrorInfo &info) {
return context->throwError(QScriptContext::UnknownError,
diff --git a/src/lib/corelib/language/artifactproperties.cpp b/src/lib/corelib/language/artifactproperties.cpp
index 00c7a2757..50fb732ab 100644
--- a/src/lib/corelib/language/artifactproperties.cpp
+++ b/src/lib/corelib/language/artifactproperties.cpp
@@ -56,18 +56,31 @@ ArtifactProperties::ArtifactProperties()
void ArtifactProperties::load(PersistentPool &pool)
{
pool.load(m_fileTagsFilter);
+ pool.load(m_extraFileTags);
pool.load(m_propertyMap);
}
void ArtifactProperties::store(PersistentPool &pool) const
{
pool.store(m_fileTagsFilter);
+ pool.store(m_extraFileTags);
pool.store(m_propertyMap);
}
+FileTags ArtifactProperties::extraFileTags() const
+{
+ return m_extraFileTags;
+}
+
+void ArtifactProperties::setExtraFileTags(const FileTags &extraFileTags)
+{
+ m_extraFileTags = extraFileTags;
+}
+
bool operator==(const ArtifactProperties &ap1, const ArtifactProperties &ap2)
{
return ap1.fileTagsFilter() == ap2.fileTagsFilter()
+ && ap1.extraFileTags() == ap2.extraFileTags()
&& *ap1.propertyMap() == *ap2.propertyMap();
}
diff --git a/src/lib/corelib/language/artifactproperties.h b/src/lib/corelib/language/artifactproperties.h
index 1e0cb0103..6f029f654 100644
--- a/src/lib/corelib/language/artifactproperties.h
+++ b/src/lib/corelib/language/artifactproperties.h
@@ -59,6 +59,9 @@ public:
PropertyMapPtr propertyMap() const { return m_propertyMap; }
void setPropertyMapInternal(const PropertyMapPtr &pmap) { m_propertyMap = pmap; }
+ FileTags extraFileTags() const;
+ void setExtraFileTags(const FileTags &extraFileTags);
+
private:
ArtifactProperties();
@@ -66,6 +69,7 @@ private:
void store(PersistentPool &) const;
FileTags m_fileTagsFilter;
+ FileTags m_extraFileTags;
PropertyMapPtr m_propertyMap;
};
diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp
index fec9d8783..32a85665d 100644
--- a/src/lib/corelib/language/builtindeclarations.cpp
+++ b/src/lib/corelib/language/builtindeclarations.cpp
@@ -71,6 +71,7 @@ BuiltinDeclarations::BuiltinDeclarations()
{ QLatin1String("Parameters"), ItemType::Parameters },
{ QLatin1String("Probe"), ItemType::Probe },
{ QLatin1String("Product"), ItemType::Product },
+ { QLatin1String("Profile"), ItemType::Profile },
{ QLatin1String("Project"), ItemType::Project },
{ QLatin1String("Properties"), ItemType::Properties }, // Callers have to handle the SubProject case.
{ QLatin1String("PropertyOptions"), ItemType::PropertyOptions },
@@ -88,6 +89,7 @@ BuiltinDeclarations::BuiltinDeclarations()
addModuleItem();
addProbeItem();
addProductItem();
+ addProfileItem();
addProjectItem();
addPropertiesItem();
addPropertyOptionsItem();
@@ -207,15 +209,13 @@ void BuiltinDeclarations::addDependsItem()
QLatin1String("true"));
item << PropertyDeclaration(QLatin1String("versionAtLeast"), PropertyDeclaration::String);
item << PropertyDeclaration(QLatin1String("versionBelow"), PropertyDeclaration::String);
- PropertyDeclaration profileDecl(QLatin1String("profiles"), PropertyDeclaration::StringList);
- profileDecl.setInitialValueSource(QLatin1String("[product.profile]"));
- item << profileDecl;
+ item << PropertyDeclaration(QLatin1String("profiles"), PropertyDeclaration::StringList);
item << PropertyDeclaration(QLatin1String("productTypes"), PropertyDeclaration::StringList);
PropertyDeclaration limitDecl(QLatin1String("limitToSubProject"), PropertyDeclaration::Boolean);
limitDecl.setInitialValueSource(QLatin1String("false"));
item << limitDecl;
- item << PropertyDeclaration(QLatin1String("multiplexConfigurationId"),
- PropertyDeclaration::String, QString(),
+ item << PropertyDeclaration(QLatin1String("multiplexConfigurationIds"),
+ PropertyDeclaration::StringList, QString(),
PropertyDeclaration::ReadOnlyFlag);
insert(item);
}
@@ -235,6 +235,7 @@ void BuiltinDeclarations::addFileTaggerItem()
item << conditionProperty();
item << PropertyDeclaration(QLatin1String("patterns"), PropertyDeclaration::StringList);
item << PropertyDeclaration(QLatin1String("fileTags"), PropertyDeclaration::StringList);
+ item << PropertyDeclaration(QLatin1String("priority"), PropertyDeclaration::Integer);
insert(item);
}
@@ -317,6 +318,7 @@ void BuiltinDeclarations::addProductItem()
<< ItemType::FileTagger
<< ItemType::Export
<< ItemType::Probe
+ << ItemType::Profile
<< ItemType::PropertyOptions
<< ItemType::Rule);
item << conditionProperty();
@@ -359,6 +361,15 @@ void BuiltinDeclarations::addProductItem()
insert(item);
}
+void BuiltinDeclarations::addProfileItem()
+{
+ ItemDeclaration item(ItemType::Profile);
+ item << conditionProperty();
+ item << nameProperty();
+ item << PropertyDeclaration(QLatin1String("baseProfile"), PropertyDeclaration::String);
+ insert(item);
+}
+
void BuiltinDeclarations::addProjectItem()
{
ItemDeclaration item(ItemType::Project);
@@ -367,6 +378,7 @@ void BuiltinDeclarations::addProjectItem()
<< ItemType::PropertyOptions
<< ItemType::SubProject
<< ItemType::Product
+ << ItemType::Profile
<< ItemType::Probe
<< ItemType::FileTagger
<< ItemType::Rule);
diff --git a/src/lib/corelib/language/builtindeclarations.h b/src/lib/corelib/language/builtindeclarations.h
index 745a5766f..8106b8a74 100644
--- a/src/lib/corelib/language/builtindeclarations.h
+++ b/src/lib/corelib/language/builtindeclarations.h
@@ -79,6 +79,7 @@ private:
static ItemDeclaration moduleLikeItem(ItemType type);
void addProbeItem();
void addProductItem();
+ void addProfileItem();
void addProjectItem();
void addPropertiesItem();
void addPropertyOptionsItem();
diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp
index 78bca0c13..34a990d67 100644
--- a/src/lib/corelib/language/evaluator.cpp
+++ b/src/lib/corelib/language/evaluator.cpp
@@ -47,11 +47,13 @@
#include "scriptengine.h"
#include "value.h"
+#include <buildgraph/buildgraph.h>
#include <jsextensions/jsextensions.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/scripttools.h>
#include <tools/qbsassert.h>
+#include <tools/qttools.h>
#include <QtCore/qdebug.h>
@@ -66,8 +68,8 @@ Evaluator::Evaluator(ScriptEngine *scriptEngine)
Evaluator::~Evaluator()
{
- for (auto it = m_scriptValueMap.cbegin(); it != m_scriptValueMap.cend(); ++it)
- delete attachedPointer<EvaluationData>(*it);
+ for (const auto &data : qAsConst(m_scriptValueMap))
+ delete attachedPointer<EvaluationData>(data);
delete m_scriptClass;
}
@@ -92,6 +94,15 @@ bool Evaluator::boolValue(const Item *item, const QString &name, bool defaultVal
return v.toBool();
}
+int Evaluator::intValue(const Item *item, const QString &name, int defaultValue,
+ bool *propertyWasSet)
+{
+ QScriptValue v;
+ if (!evaluateProperty(&v, item, name, propertyWasSet))
+ return defaultValue;
+ return v.toInt32();
+}
+
FileTags Evaluator::fileTagsValue(const Item *item, const QString &name, bool *propertySet)
{
return FileTags::fromStringList(stringListValue(item, name, propertySet));
@@ -190,6 +201,16 @@ void Evaluator::handleEvaluationError(const Item *item, const QString &name,
throw ErrorInfo(message, CodeLocation(filePath, line, -1, false));
}
+void Evaluator::setPathPropertiesBaseDir(const QString &dirPath)
+{
+ m_scriptClass->setPathPropertiesBaseDir(dirPath);
+}
+
+void Evaluator::clearPathPropertiesBaseDir()
+{
+ m_scriptClass->clearPathPropertiesBaseDir();
+}
+
bool Evaluator::evaluateProperty(QScriptValue *result, const Item *item, const QString &name,
bool *propertyWasSet)
{
@@ -219,8 +240,7 @@ Evaluator::FileContextScopes Evaluator::fileContextScopes(const FileContextConst
if (!result.importScope.isObject()) {
try {
result.importScope = m_scriptEngine->newObject();
- m_scriptEngine->import(file, result.importScope);
- JsExtensions::setupExtensions(file->jsExtensions(), result.importScope);
+ setupScriptEngineForFile(m_scriptEngine, file, result.importScope);
} catch (const ErrorInfo &e) {
result.importScope = m_scriptEngine->currentContext()->throwError(e.toString());
}
diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h
index c86f0375d..840c808ed 100644
--- a/src/lib/corelib/language/evaluator.h
+++ b/src/lib/corelib/language/evaluator.h
@@ -69,6 +69,8 @@ public:
QScriptValue value(const Item *item, const QString &name, bool *propertySet = 0);
bool boolValue(const Item *item, const QString &name, bool defaultValue = false,
bool *propertyWasSet = 0);
+ int intValue(const Item *item, const QString &name, int defaultValue = 0,
+ bool *propertyWasSet = 0);
FileTags fileTagsValue(const Item *item, const QString &name, bool *propertySet = 0);
QString stringValue(const Item *item, const QString &name,
const QString &defaultValue = QString(), bool *propertyWasSet = 0);
@@ -91,6 +93,10 @@ public:
void handleEvaluationError(const Item *item, const QString &name,
const QScriptValue &scriptValue);
+
+ void setPathPropertiesBaseDir(const QString &dirPath);
+ void clearPathPropertiesBaseDir();
+
private:
void onItemPropertyChanged(Item *item);
bool evaluateProperty(QScriptValue *result, const Item *item, const QString &name,
diff --git a/src/lib/corelib/language/evaluatorscriptclass.cpp b/src/lib/corelib/language/evaluatorscriptclass.cpp
index ba7df02b5..91f9b241a 100755..100644
--- a/src/lib/corelib/language/evaluatorscriptclass.cpp
+++ b/src/lib/corelib/language/evaluatorscriptclass.cpp
@@ -142,7 +142,7 @@ private:
scriptContext->popScope();
}
- void handle(JSSourceValue *value)
+ void handle(JSSourceValue *value) override
{
const Item *conditionScopeItem = 0;
QScriptValue conditionScope;
@@ -288,14 +288,14 @@ private:
popScopes();
}
- void handle(ItemValue *value)
+ void handle(ItemValue *value) override
{
*result = data->evaluator->scriptValue(value->item());
if (!result->isValid())
qDebug() << "SVConverter returned invalid script value.";
}
- void handle(VariantValue *variantValue)
+ void handle(VariantValue *variantValue) override
{
*result = engine->toScriptValue(variantValue->value());
}
@@ -384,7 +384,7 @@ QString EvaluatorScriptClass::resultToString(const QScriptValue &scriptValue)
{
return (scriptValue.isObject()
? QLatin1String("[Object: ")
- + QString::number(scriptValue.objectId(), 16) + QLatin1Char(']')
+ + QString::number(scriptValue.objectId()) + QLatin1Char(']')
: scriptValue.toVariant().toString());
}
@@ -429,10 +429,10 @@ void EvaluatorScriptClass::collectValuesFromNextChain(const EvaluationData *data
}
}
-static QString overriddenSourceDirectory(const Item *item)
+static QString overriddenSourceDirectory(const Item *item, const QString &defaultValue)
{
const VariantValuePtr v = item->variantProperty(QLatin1String("_qbs_sourceDir"));
- return v ? v->value().toString() : QString();
+ return v ? v->value().toString() : defaultValue;
}
static void makeTypeError(const ErrorInfo &error, QScriptValue &v)
@@ -449,7 +449,8 @@ static void makeTypeError(const PropertyDeclaration &decl, const CodeLocation &l
makeTypeError(error, v);
}
-static void convertToPropertyType(const Item *item, const PropertyDeclaration& decl,
+static void convertToPropertyType(const QString &pathPropertiesBaseDir, const Item *item,
+ const PropertyDeclaration& decl,
const Value *value, QScriptValue &v)
{
if (value->type() == Value::VariantValueType && v.isUndefined() && !decl.isScalar()) {
@@ -479,9 +480,10 @@ static void convertToPropertyType(const Item *item, const PropertyDeclaration& d
makeTypeError(decl, location, v);
break;
}
- const QString srcDir = overriddenSourceDirectory(item);
+ const QString srcDir = overriddenSourceDirectory(item, pathPropertiesBaseDir);
if (!srcDir.isEmpty())
- v = v.engine()->toScriptValue(FileInfo::resolvePath(srcDir, v.toString()));
+ v = v.engine()->toScriptValue(QDir::cleanPath(
+ FileInfo::resolvePath(srcDir, v.toString())));
break;
}
case PropertyDeclaration::String:
@@ -489,7 +491,7 @@ static void convertToPropertyType(const Item *item, const PropertyDeclaration& d
makeTypeError(decl, location, v);
break;
case PropertyDeclaration::PathList:
- srcDir = overriddenSourceDirectory(item);
+ srcDir = overriddenSourceDirectory(item, pathPropertiesBaseDir);
// Fall-through.
case PropertyDeclaration::StringList:
{
@@ -521,7 +523,8 @@ static void convertToPropertyType(const Item *item, const PropertyDeclaration& d
}
if (srcDir.isEmpty())
continue;
- elem = v.engine()->toScriptValue(FileInfo::resolvePath(srcDir, elem.toString()));
+ elem = v.engine()->toScriptValue(
+ QDir::cleanPath(FileInfo::resolvePath(srcDir, elem.toString())));
v.setProperty(i, elem);
}
break;
@@ -614,7 +617,7 @@ QScriptValue EvaluatorScriptClass::property(const QScriptValue &object, const QS
converter.start();
const PropertyDeclaration decl = data->item->propertyDeclaration(name.toString());
- convertToPropertyType(data->item, decl, value.get(), result);
+ convertToPropertyType(m_pathPropertiesBaseDir, data->item, decl, value.get(), result);
}
if (debugProperties)
diff --git a/src/lib/corelib/language/evaluatorscriptclass.h b/src/lib/corelib/language/evaluatorscriptclass.h
index 865d9e971..db718041b 100755
--- a/src/lib/corelib/language/evaluatorscriptclass.h
+++ b/src/lib/corelib/language/evaluatorscriptclass.h
@@ -77,6 +77,9 @@ public:
PropertyDependencies propertyDependencies() const { return m_propertyDependencies; }
void clearPropertyDependencies() { m_propertyDependencies.clear(); }
+ void setPathPropertiesBaseDir(const QString &dirPath) { m_pathPropertiesBaseDir = dirPath; }
+ void clearPathPropertiesBaseDir() { m_pathPropertiesBaseDir.clear(); }
+
private:
QueryFlags queryItemProperty(const EvaluationData *data,
const QString &name,
@@ -115,6 +118,7 @@ private:
Set<Value *> m_currentNextChain;
PropertyDependencies m_propertyDependencies;
std::stack<QualifiedId> m_requestedProperties;
+ QString m_pathPropertiesBaseDir;
};
} // namespace Internal
diff --git a/src/lib/corelib/language/filecontextbase.cpp b/src/lib/corelib/language/filecontextbase.cpp
index 96a383faf..70dd04a0f 100644
--- a/src/lib/corelib/language/filecontextbase.cpp
+++ b/src/lib/corelib/language/filecontextbase.cpp
@@ -49,13 +49,5 @@ QString FileContextBase::dirPath() const
return FileInfo::path(m_filePath);
}
-FileContextBase::FileContextBase(const FileContextBase &other)
- : m_filePath(other.m_filePath)
- , m_jsImports(other.m_jsImports)
- , m_jsExtensions(other.m_jsExtensions)
- , m_searchPaths(other.m_searchPaths)
-{
-}
-
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/filecontextbase.h b/src/lib/corelib/language/filecontextbase.h
index 8b64141d7..335e54253 100644
--- a/src/lib/corelib/language/filecontextbase.h
+++ b/src/lib/corelib/language/filecontextbase.h
@@ -63,8 +63,9 @@ public:
QString dirPath() const;
protected:
- FileContextBase() {}
- FileContextBase(const FileContextBase &other);
+ FileContextBase() = default;
+ FileContextBase(const FileContextBase &other) = default;
+ FileContextBase(FileContextBase &&other) = default;
QString m_filePath;
JsImports m_jsImports;
diff --git a/src/lib/corelib/language/filetags.cpp b/src/lib/corelib/language/filetags.cpp
index e15be6f6f..08e49fe36 100644
--- a/src/lib/corelib/language/filetags.cpp
+++ b/src/lib/corelib/language/filetags.cpp
@@ -83,5 +83,11 @@ LogWriter operator <<(LogWriter w, const FileTags &tags)
return w;
}
+QDebug operator<<(QDebug debug, const FileTags &tags)
+{
+ QDebugStateSaver saver(debug);
+ return debug.resetFormat().noquote() << tags.toStringList();
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/filetags.h b/src/lib/corelib/language/filetags.h
index cbcc44231..94dd9e140 100644
--- a/src/lib/corelib/language/filetags.h
+++ b/src/lib/corelib/language/filetags.h
@@ -86,6 +86,7 @@ public:
};
LogWriter operator <<(LogWriter w, const FileTags &tags);
+QDebug operator<<(QDebug debug, const FileTags &tags);
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp
index 26a08ace0..8299611a5 100644
--- a/src/lib/corelib/language/item.cpp
+++ b/src/lib/corelib/language/item.cpp
@@ -188,10 +188,17 @@ VariantValuePtr Item::variantProperty(const QString &name) const
return std::static_pointer_cast<VariantValue>(v);
}
-PropertyDeclaration Item::propertyDeclaration(const QString &name) const
+PropertyDeclaration Item::propertyDeclaration(const QString &name, bool allowExpired) const
{
- const PropertyDeclaration decl = m_propertyDeclarations.value(name);
- return (!decl.isValid() && m_prototype) ? m_prototype->propertyDeclaration(name) : decl;
+ PropertyDeclaration decl = m_propertyDeclarations.value(name);
+ if (decl.isValid())
+ return decl;
+ if (allowExpired) {
+ decl = m_expiredPropertyDeclarations.value(name);
+ if (decl.isValid())
+ return decl;
+ }
+ return m_prototype ? m_prototype->propertyDeclaration(name) : decl;
}
void Item::addModule(const Item::Module &module)
@@ -347,7 +354,12 @@ void Item::addChild(Item *parent, Item *child)
void Item::setPropertyDeclaration(const QString &name, const PropertyDeclaration &declaration)
{
- m_propertyDeclarations.insert(name, declaration);
+ if (declaration.isExpired()) {
+ m_propertyDeclarations.remove(name);
+ m_expiredPropertyDeclarations.insert(name, declaration);
+ } else {
+ m_propertyDeclarations.insert(name, declaration);
+ }
}
void Item::setPropertyDeclarations(const Item::PropertyDeclarationMap &decls)
diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h
index f2ca71aac..08c7aadd7 100644
--- a/src/lib/corelib/language/item.h
+++ b/src/lib/corelib/language/item.h
@@ -101,7 +101,7 @@ public:
Item *child(ItemType type, bool checkForMultiple = true) const;
const PropertyMap &properties() const { return m_properties; }
const PropertyDeclarationMap &propertyDeclarations() const { return m_propertyDeclarations; }
- PropertyDeclaration propertyDeclaration(const QString &name) const;
+ PropertyDeclaration propertyDeclaration(const QString &name, bool allowExpired = true) const;
const Modules &modules() const { return m_modules; }
void addModule(const Module &module);
void removeModules() { m_modules.clear(); }
@@ -156,6 +156,7 @@ private:
FileContextPtr m_file;
PropertyMap m_properties;
PropertyDeclarationMap m_propertyDeclarations;
+ PropertyDeclarationMap m_expiredPropertyDeclarations;
Modules m_modules;
ItemType m_type;
};
diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp
index 564fe1323..b7e00de1c 100644
--- a/src/lib/corelib/language/itemreader.cpp
+++ b/src/lib/corelib/language/itemreader.cpp
@@ -43,6 +43,8 @@
#include <tools/profiling.h>
+#include <algorithm>
+
namespace qbs {
namespace Internal {
@@ -58,16 +60,19 @@ ItemReader::~ItemReader()
void ItemReader::setSearchPaths(const QStringList &searchPaths)
{
m_searchPaths = searchPaths;
+ m_allSearchPaths.clear();
}
void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths)
{
m_extraSearchPaths.push_back(extraSearchPaths);
+ m_allSearchPaths.clear();
}
void ItemReader::popExtraSearchPaths()
{
m_extraSearchPaths.pop_back();
+ m_allSearchPaths.clear();
}
std::vector<QStringList> ItemReader::extraSearchPathsStack() const
@@ -75,19 +80,35 @@ std::vector<QStringList> ItemReader::extraSearchPathsStack() const
return m_extraSearchPaths;
}
-QStringList ItemReader::searchPaths() const
+void ItemReader::setExtraSearchPathsStack(const std::vector<QStringList> &s)
+{
+ m_extraSearchPaths = s;
+ m_allSearchPaths.clear();
+}
+
+void ItemReader::clearExtraSearchPathsStack()
+{
+ m_extraSearchPaths.clear();
+ m_allSearchPaths.clear();
+}
+
+const QStringList &ItemReader::allSearchPaths() const
{
- QStringList paths;
- for (auto it = m_extraSearchPaths.crbegin(), end = m_extraSearchPaths.crend(); it != end; ++it)
- paths += *it;
- paths += m_searchPaths;
- return paths;
+ if (m_allSearchPaths.isEmpty()) {
+ std::for_each(m_extraSearchPaths.crbegin(), m_extraSearchPaths.crend(),
+ [this] (const QStringList &paths) {
+ m_allSearchPaths += paths;
+ });
+ m_allSearchPaths += m_searchPaths;
+ m_allSearchPaths.removeDuplicates();
+ }
+ return m_allSearchPaths;
}
Item *ItemReader::readFile(const QString &filePath)
{
AccumulatingTimer readFileTimer(m_elapsedTime != -1 ? &m_elapsedTime : nullptr);
- return m_visitorState->readFile(filePath, searchPaths(), m_pool);
+ return m_visitorState->readFile(filePath, allSearchPaths(), m_pool);
}
Set<QString> ItemReader::filesRead() const
diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/language/itemreader.h
index 293fea996..56a849d96 100644
--- a/src/lib/corelib/language/itemreader.h
+++ b/src/lib/corelib/language/itemreader.h
@@ -75,9 +75,9 @@ public:
void pushExtraSearchPaths(const QStringList &extraSearchPaths);
void popExtraSearchPaths();
std::vector<QStringList> extraSearchPathsStack() const;
- void setExtraSearchPathsStack(const std::vector<QStringList> &s) { m_extraSearchPaths = s; }
- void clearExtraSearchPathsStack() { m_extraSearchPaths.clear(); }
- QStringList searchPaths() const;
+ void setExtraSearchPathsStack(const std::vector<QStringList> &s);
+ void clearExtraSearchPathsStack();
+ const QStringList &allSearchPaths() const;
Item *readFile(const QString &filePath);
@@ -90,6 +90,7 @@ private:
ItemPool *m_pool = nullptr;
QStringList m_searchPaths;
std::vector<QStringList> m_extraSearchPaths;
+ mutable QStringList m_allSearchPaths;
ItemReaderVisitorState * const m_visitorState;
qint64 m_elapsedTime = -1;
};
diff --git a/src/lib/corelib/language/itemtype.h b/src/lib/corelib/language/itemtype.h
index 4901cbbe4..c0e76c94b 100644
--- a/src/lib/corelib/language/itemtype.h
+++ b/src/lib/corelib/language/itemtype.h
@@ -58,6 +58,7 @@ enum class ItemType {
Parameters,
Probe,
Product,
+ Profile,
Project,
Properties,
PropertiesInSubProject,
diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp
index e2aa3dbe6..3ac4f089f 100644
--- a/src/lib/corelib/language/language.cpp
+++ b/src/lib/corelib/language/language.cpp
@@ -45,11 +45,13 @@
#include "scriptengine.h"
#include <buildgraph/artifact.h>
+#include <buildgraph/buildgraph.h>
#include <buildgraph/productbuilddata.h>
#include <buildgraph/projectbuilddata.h>
#include <buildgraph/rulegraph.h> // TODO: Move to language?
#include <buildgraph/transformer.h>
#include <jsextensions/jsextensions.h>
+#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
#include <tools/hostosinfo.h>
@@ -68,6 +70,7 @@
#include <QtScript/qscriptvalue.h>
#include <algorithm>
+#include <memory>
#include <mutex>
namespace qbs {
@@ -83,8 +86,8 @@ template<typename T> bool equals(const T *v1, const T *v2)
}
-FileTagger::FileTagger(const QStringList &patterns, const FileTags &fileTags)
- : m_fileTags(fileTags)
+FileTagger::FileTagger(const QStringList &patterns, const FileTags &fileTags, int priority)
+ : m_fileTags(fileTags), m_priority(priority)
{
setPatterns(patterns);
}
@@ -106,6 +109,7 @@ void FileTagger::load(PersistentPool &pool)
{
setPatterns(pool.load<QStringList>());
pool.load(m_fileTags);
+ pool.load(m_priority);
}
void FileTagger::store(PersistentPool &pool) const
@@ -115,6 +119,7 @@ void FileTagger::store(PersistentPool &pool) const
patterns << regExp.pattern();
pool.store(patterns);
pool.store(m_fileTags);
+ pool.store(m_priority);
}
@@ -166,7 +171,6 @@ void SourceArtifactInternal::store(PersistentPool &pool) const
void SourceWildCards::load(PersistentPool &pool)
{
- pool.load(prefix);
pool.load(patterns);
pool.load(excludePatterns);
pool.load(dirTimeStamps);
@@ -175,7 +179,6 @@ void SourceWildCards::load(PersistentPool &pool)
void SourceWildCards::store(PersistentPool &pool) const
{
- pool.store(prefix);
pool.store(patterns);
pool.store(excludePatterns);
pool.store(dirTimeStamps);
@@ -220,6 +223,8 @@ void ResolvedGroup::load(PersistentPool &pool)
pool.load(prefix);
pool.load(files);
pool.load(wildcards);
+ if (wildcards)
+ wildcards->group = this;
pool.load(properties);
pool.load(fileTags);
pool.load(overrideTags);
@@ -367,6 +372,11 @@ bool operator==(const ResolvedModule &m1, const ResolvedModule &m2)
&& equals(m1.setupRunEnvironmentScript.get(), m2.setupRunEnvironmentScript.get());
}
+RulePtr Rule::clone() const
+{
+ return std::make_shared<Rule>(*this);
+}
+
QString Rule::toString() const
{
QStringList outputTagsSorted = collectedOutputFileTags().toStringList();
@@ -395,7 +405,12 @@ FileTags Rule::staticOutputFileTags() const
FileTags Rule::collectedOutputFileTags() const
{
- return outputFileTags.isEmpty() ? staticOutputFileTags() : outputFileTags;
+ FileTags result = outputFileTags.isEmpty() ? staticOutputFileTags() : outputFileTags;
+ for (const auto &ap : qAsConst(product->artifactProperties)) {
+ if (ap->fileTagsFilter().intersects(result))
+ result += ap->extraFileTags();
+ }
+ return result;
}
bool Rule::isDynamic() const
@@ -490,9 +505,19 @@ QList<SourceArtifactPtr> ResolvedProduct::allEnabledFiles() const
FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const
{
FileTags result;
+ std::unique_ptr<int> priority;
for (const FileTaggerConstPtr &tagger : qAsConst(fileTaggers)) {
for (const QRegExp &pattern : tagger->patterns()) {
if (FileInfo::globMatches(pattern, fileName)) {
+ if (priority) {
+ if (*priority != tagger->priority()) {
+ // The taggers are expected to be sorted by priority.
+ QBS_ASSERT(*priority > tagger->priority(), return result);
+ return result;
+ }
+ } else {
+ priority.reset(new int(tagger->priority()));
+ }
result.unite(tagger->fileTags());
break;
}
@@ -516,6 +541,8 @@ void ResolvedProduct::load(PersistentPool &pool)
pool.load(productProperties);
pool.load(moduleProperties);
pool.load(rules);
+ for (const RulePtr &rule : rules)
+ rule->product = this;
pool.load(dependencies);
pool.load(dependencyParameters);
pool.load(fileTaggers);
@@ -657,9 +684,7 @@ static QProcessEnvironment getProcessEnvironment(ScriptEngine *engine, EnvType e
setupScript = module->setupRunEnvironmentScript;
}
- // handle imports
- engine->import(setupScript->fileContext, scope);
- JsExtensions::setupExtensions(setupScript->fileContext->jsExtensions(), scope);
+ setupScriptEngineForFile(engine, setupScript->fileContext, scope);
// expose properties of direct module dependencies
QScriptValue scriptValue;
@@ -782,6 +807,20 @@ QString ResolvedProduct::uniqueName() const
return uniqueName(name, multiplexConfigurationId);
}
+QString ResolvedProduct::fullDisplayName(const QString &name,
+ const QString &multiplexConfigurationId)
+{
+ QString result = name;
+ if (!multiplexConfigurationId.isEmpty())
+ result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexConfigurationId));
+ return result;
+}
+
+QString ResolvedProduct::fullDisplayName() const
+{
+ return fullDisplayName(name, multiplexConfigurationId);
+}
+
static QStringList findGeneratedFiles(const Artifact *base, bool recursive, const FileTags &tags)
{
QStringList result;
@@ -972,11 +1011,11 @@ void TopLevelProject::store(Logger logger) const
if (!buildData)
return;
if (!buildData->isDirty) {
- logger.qbsDebug() << "[BG] build graph is unchanged in project " << id() << ".";
+ qCDebug(lcBuildGraph) << "build graph is unchanged in project" << id();
return;
}
const QString fileName = buildGraphFilePath();
- logger.qbsDebug() << "[BG] storing: " << fileName;
+ qCDebug(lcBuildGraph) << "storing:" << fileName;
PersistentPool pool(logger);
PersistentPool::HeadData headData;
headData.projectConfig = buildConfiguration();
@@ -991,7 +1030,6 @@ void TopLevelProject::load(PersistentPool &pool)
{
ResolvedProject::load(pool);
pool.load(m_id);
- pool.load(usedEnvironment);
pool.load(canonicalFilePathResults);
pool.load(fileExistsResults);
pool.load(directoryEntriesResults);
@@ -1012,7 +1050,6 @@ void TopLevelProject::store(PersistentPool &pool) const
{
ResolvedProject::store(pool);
pool.store(m_id);
- pool.store(usedEnvironment);
pool.store(canonicalFilePathResults);
pool.store(fileExistsResults);
pool.store(directoryEntriesResults);
@@ -1069,7 +1106,7 @@ Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group,
const QStringList &patterns, const QString &baseDir, const QString &buildDir)
{
Set<QString> files;
- QString expandedPrefix = prefix;
+ QString expandedPrefix = group->prefix;
if (expandedPrefix.startsWith(QLatin1String("~/")))
expandedPrefix.replace(0, 1, QDir::homePath());
for (QString pattern : patterns) {
@@ -1282,5 +1319,10 @@ void ResolvedScanner::store(PersistentPool &pool) const
pool.store(scanScript);
}
+QString multiplexIdToString(const QString &id)
+{
+ return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8()));
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h
index d65a12536..313ba63f4 100644
--- a/src/lib/corelib/language/language.h
+++ b/src/lib/corelib/language/language.h
@@ -78,15 +78,17 @@ class FileTagger : public PersistentObject
{
public:
static FileTaggerPtr create() { return FileTaggerPtr(new FileTagger); }
- static FileTaggerPtr create(const QStringList &patterns, const FileTags &fileTags) {
- return FileTaggerPtr(new FileTagger(patterns, fileTags));
+ static FileTaggerPtr create(const QStringList &patterns, const FileTags &fileTags,
+ int priority) {
+ return FileTaggerPtr(new FileTagger(patterns, fileTags, priority));
}
const QList<QRegExp> &patterns() const { return m_patterns; }
const FileTags &fileTags() const { return m_fileTags; }
+ int priority() const { return m_priority; }
private:
- FileTagger(const QStringList &patterns, const FileTags &fileTags);
+ FileTagger(const QStringList &patterns, const FileTags &fileTags, int priority);
FileTagger() {}
void setPatterns(const QStringList &patterns);
@@ -96,6 +98,7 @@ private:
QList<QRegExp> m_patterns;
FileTags m_fileTags;
+ int m_priority = 0;
};
class Probe : public PersistentObject
@@ -223,25 +226,16 @@ bool sourceArtifactSetsAreEqual(const QList<SourceArtifactPtr> &l1,
class SourceWildCards : public PersistentObject
{
public:
- typedef std::shared_ptr<SourceWildCards> Ptr;
- typedef std::shared_ptr<const SourceWildCards> ConstPtr;
-
- static Ptr create() { return Ptr(new SourceWildCards); }
-
Set<QString> expandPatterns(const GroupConstPtr &group, const QString &baseDir,
const QString &buildDir);
- // TODO: Use back pointer to Group instead?
- QString prefix;
-
+ const ResolvedGroup *group = nullptr; // The owning group.
QStringList patterns;
QStringList excludePatterns;
std::vector<std::pair<QString, FileTime>> dirTimeStamps;
QList<SourceArtifactPtr> files;
private:
- SourceWildCards() {}
-
Set<QString> expandPatterns(const GroupConstPtr &group, const QStringList &patterns,
const QString &baseDir, const QString &buildDir);
void expandPatterns(Set<QString> &result, const GroupConstPtr &group,
@@ -263,7 +257,7 @@ public:
bool enabled;
QString prefix;
QList<SourceArtifactPtr> files;
- SourceWildCards::Ptr wildcards;
+ std::unique_ptr<SourceWildCards> wildcards;
PropertyMapPtr properties;
FileTags fileTags;
bool overrideTags;
@@ -337,7 +331,9 @@ class Rule : public PersistentObject
{
public:
static RulePtr create() { return RulePtr(new Rule); }
+ RulePtr clone() const;
+ ResolvedProduct *product = nullptr; // The owning product.
ResolvedModuleConstPtr module;
QString name;
ScriptFunctionPtr prepareScript;
@@ -414,7 +410,7 @@ public:
WeakPointer<ResolvedProject> project;
QVariantMap productProperties;
PropertyMapPtr moduleProperties;
- Set<RulePtr> rules;
+ QList<RulePtr> rules;
Set<ResolvedProductPtr> dependencies;
QHash<ResolvedProductConstPtr, QVariantMap> dependencyParameters;
QList<FileTaggerConstPtr> fileTaggers;
@@ -450,6 +446,8 @@ public:
static QString uniqueName(const QString &name,
const QString &multiplexConfigurationId);
QString uniqueName() const;
+ static QString fullDisplayName(const QString &name, const QString &multiplexConfigurationId);
+ QString fullDisplayName() const;
QStringList generatedFiles(const QString &baseFile, bool recursive, const FileTags &tags) const;
@@ -519,12 +517,6 @@ public:
QProcessEnvironment environment;
QList<ProbeConstPtr> probes;
- // Environment variables requested by the project while resolving.
- // TODO: This information is currently not used. Remove in 1.5 or use elaborate change tracking
- // logic where rules declare the environment variables that could influence their
- // behavior.
- QHash<QString, QString> usedEnvironment;
-
QHash<QString, QString> canonicalFilePathResults; // Results of calls to "File.canonicalFilePath()."
QHash<QString, bool> fileExistsResults; // Results of calls to "File.exists()".
QHash<std::pair<QString, quint32>, QStringList> directoryEntriesResults; // Results of calls to "File.directoryEntries()".
@@ -560,6 +552,8 @@ private:
bool artifactPropertyListsAreEqual(const QList<ArtifactPropertiesPtr> &l1,
const QList<ArtifactPropertiesPtr> &l2);
+QString multiplexIdToString(const QString &id);
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp
index 232c91a2c..b226dc1fd 100755..100644
--- a/src/lib/corelib/language/moduleloader.cpp
+++ b/src/lib/corelib/language/moduleloader.cpp
@@ -51,6 +51,7 @@
#include "value.h"
#include <language/language.h>
+#include <logging/categories.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/error.h>
@@ -88,8 +89,6 @@ static void handlePropertyError(const ErrorInfo &error, const SetupProjectParame
class ModuleLoader::ItemModuleList : public QList<Item::Module> {};
-const QString moduleSearchSubDir = QLatin1String("modules");
-
static QString probeGlobalId(Item *probe)
{
QString id;
@@ -162,26 +161,13 @@ public:
});
if (it == deps.end()) {
QBS_CHECK(!productContext->multiplexConfigurationId.isEmpty());
- ErrorInfo e(Tr::tr("Dependency from product '%1' to product '%2' not "
- "fulfilled.")
- .arg(productContext->name, dep.name),
- productContext->item->location());
- const QString configJsonString = MultiplexInfo::configurationStringFromId(
- productContext->multiplexConfigurationId);
- const QVariantMap config = QJsonDocument::fromJson(
- configJsonString.toUtf8()).object().toVariantMap();
- QString userConfigString;
- for (auto it = config.cbegin(); it != config.cend(); ++it) {
- if (!userConfigString.isEmpty())
- userConfigString += QLatin1Char('\n');
- userConfigString.append(QLatin1String("\tqbs.") + it.key())
- .append(QLatin1String(": "))
- .append(it.value().toStringList().join(QLatin1Char(',')));
- }
- e.append(Tr::tr("No product '%1' found with a matching multiplex "
- "configuration:\n%2").arg(dep.name, userConfigString));
- throw e;
-
+ const QString productName = ResolvedProduct::fullDisplayName(
+ productContext->name, productContext->multiplexConfigurationId);
+ const QString depName = ResolvedProduct::fullDisplayName(
+ dep.name, dep.multiplexConfigurationId);
+ throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not "
+ "fulfilled.").arg(productName, depName),
+ productContext->item->location());
}
productDependencies.push_back(*it);
allDependencies << *it;
@@ -271,25 +257,10 @@ void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver)
m_progressObserver = progressObserver;
}
-static void addExtraModuleSearchPath(QStringList &list, const QString &searchPath)
-{
- list += FileInfo::resolvePath(searchPath, moduleSearchSubDir);
-}
-
void ModuleLoader::setSearchPaths(const QStringList &searchPaths)
{
m_reader->setSearchPaths(searchPaths);
-
- m_moduleDirListCache.clear();
- m_moduleSearchPaths.clear();
- for (const QString &path : searchPaths)
- addExtraModuleSearchPath(m_moduleSearchPaths, path);
-
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "[MODLDR] module search paths:";
- for (const QString &path : qAsConst(m_moduleSearchPaths))
- m_logger.qbsTrace() << " " << path;
- }
+ qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths;
}
void ModuleLoader::setOldProjectProbes(const QList<ProbeConstPtr> &oldProbes)
@@ -313,8 +284,7 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters &parameters)
{
TimedActivityLogger moduleLoaderTimer(m_logger, Tr::tr("ModuleLoader"),
parameters.logElapsedTime());
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] load" << parameters.projectFilePath();
+ qCDebug(lcModuleLoader) << "load" << parameters.projectFilePath();
m_parameters = parameters;
m_modulePrototypeItemCache.clear();
m_parameterDeclarations.clear();
@@ -322,6 +292,7 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters &parameters)
m_reader->clearExtraSearchPathsStack();
m_reader->setEnableTiming(parameters.logElapsedTime());
m_elapsedTimeProbes = 0;
+ m_settings.reset(new Settings(parameters.settingsDirectory()));
for (const QString &key : m_parameters.overriddenValues().keys()) {
static const QStringList prefixes({ QLatin1String("project"), QLatin1String("projects"),
@@ -384,6 +355,8 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters &parameters)
Set<QString>() << QDir::cleanPath(parameters.projectFilePath()));
result.root = root;
result.qbsFiles = m_reader->filesRead();
+ for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it)
+ result.profileConfigs.remove(it.key());
printProfilingInfo();
return result;
}
@@ -393,6 +366,8 @@ class PropertyDeclarationCheck : public ValueHandler
const Set<Item *> &m_disabledItems;
Set<Item *> m_handledItems;
Item *m_parentItem;
+ Item *m_currentModuleInstance = nullptr;
+ QualifiedId m_currentModuleName;
QString m_currentName;
SetupProjectParameters m_params;
Logger &m_logger;
@@ -412,7 +387,7 @@ public:
}
private:
- void handle(JSSourceValue *value)
+ void handle(JSSourceValue *value) override
{
if (!value->createdByPropertiesBlock()) {
const ErrorInfo error(Tr::tr("Property '%1' is not declared.")
@@ -421,11 +396,30 @@ private:
}
}
- void handle(ItemValue *value)
+ void handle(ItemValue *value) override
+ {
+ if (checkItemValue(value))
+ handleItem(value->item());
+ }
+
+ bool checkItemValue(ItemValue *value)
{
// TODO: Remove once QBS-1030 is fixed.
if (m_parentItem->type() == ItemType::Artifact)
- return;
+ return false;
+
+ if (m_parentItem->type() == ItemType::Export) {
+ // Export item prototypes do not have instantiated modules.
+ // The module instances are where the Export is used.
+ QBS_ASSERT(m_currentModuleInstance, return false);
+ auto it = std::find_if(m_currentModuleInstance->modules().cbegin(),
+ m_currentModuleInstance->modules().cend(),
+ [this] (const Item::Module &m) {
+ return m.name == m_currentModuleName;
+ });
+ if (it != m_currentModuleInstance->modules().cend())
+ return true;
+ }
if ((value->item()->type() != ItemType::ModuleInstance || !value->item()->scope())
&& value->item()->type() != ItemType::ModulePrefix
@@ -438,9 +432,10 @@ private:
value->location().isValid() ? value->location()
: m_parentItem->location());
handlePropertyError(error, m_params, m_logger);
- } else {
- handleItem(value->item());
+ return false;
}
+
+ return true;
}
void handleItem(Item *item)
@@ -491,7 +486,12 @@ private:
continue;
}
m_currentName = it.key();
+ const QualifiedId oldModuleName = m_currentModuleName;
+ if (m_parentItem->type() != ItemType::ModulePrefix)
+ m_currentModuleName.clear();
+ m_currentModuleName.append(m_currentName);
it.value()->apply(this);
+ m_currentModuleName = oldModuleName;
}
m_parentItem = oldParentItem;
for (Item * const child : item->children()) {
@@ -510,11 +510,15 @@ private:
// only exist in the prototype of an Export item, not in the instance.
// Example 1 - setting a property of an unknown module: Export { abc.def: true }
// Example 2 - setting a non-existing Export property: Export { blubb: true }
- if (item->type() == ItemType::ModuleInstance && item->prototype())
+ if (item->type() == ItemType::ModuleInstance && item->prototype()) {
+ Item *oldInstance = m_currentModuleInstance;
+ m_currentModuleInstance = item;
handleItem(item->prototype());
+ m_currentModuleInstance = oldInstance;
+ }
}
- void handle(VariantValue *) { /* only created internally - no need to check */ }
+ void handle(VariantValue *) override { /* only created internally - no need to check */ }
};
void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem,
@@ -604,6 +608,8 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult,
m_qbsVersion.toString()));
}
+ handleProfileItems(projectItem, &projectContext);
+
QList<Item *> multiplexedProducts;
for (Item * const child : projectItem->children()) {
child->setScope(projectContext.scope);
@@ -666,11 +672,6 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult,
m_reader->popExtraSearchPaths();
}
-QString ModuleLoader::MultiplexInfo::configurationStringFromId(const QString &idString)
-{
- return QString::fromUtf8(QByteArray::fromBase64(idString.toUtf8()));
-}
-
QString ModuleLoader::MultiplexInfo::toIdString(size_t row) const
{
const auto &mprow = table.at(row);
@@ -851,7 +852,7 @@ QList<Item *> ModuleLoader::multiplexProductItem(ProductContext *dummyContext, I
dependsItem->setProperty(nameKey, productNameValue);
dependsItem->setProperty(multiplexConfigurationIdKey, v);
dependsItem->setProperty(QStringLiteral("profiles"),
- VariantValue::create(QStringLiteral("*")));
+ VariantValue::create(QStringList()));
Item::addChild(aggregator, dependsItem);
}
}
@@ -870,7 +871,7 @@ void ModuleLoader::adjustDependenciesForMultiplexing(const TopLevelProjectContex
void ModuleLoader::adjustDependenciesForMultiplexing(const ModuleLoader::ProductContext &product)
{
static const QString multiplexConfigurationIdKey = QStringLiteral("multiplexConfigurationId");
- std::vector<Item *> additionalDependsItems;
+ static const QString multiplexConfigurationIdsKey = QStringLiteral("multiplexConfigurationIds");
for (Item *dependsItem : product.item->children()) {
if (dependsItem->type() != ItemType::Depends)
continue;
@@ -908,39 +909,32 @@ void ModuleLoader::adjustDependenciesForMultiplexing(const ModuleLoader::Product
if (!productIsMultiplexed && hasNonMultiplexedDependency)
continue;
- for (std::size_t i = 0; i < dependencies.size(); ++i) {
- const QString depMultiplexId = dependencies.at(i)->multiplexConfigurationId;
- if (i == 0) {
- if (productIsMultiplexed) { // (2)
- dependsItem->setProperty(multiplexConfigurationIdKey,
- product.item->property(multiplexConfigurationIdKey));
- break;
- }
- // (3b)
- dependsItem->setProperty(multiplexConfigurationIdKey,
- VariantValue::create(depMultiplexId));
- } else {
- // (3b)
- Item * const newDependsItem = dependsItem->clone();
- newDependsItem->setProperty(multiplexConfigurationIdKey,
- VariantValue::create(depMultiplexId));
- dependsItem->setProperty(QStringLiteral("profiles"),
- VariantValue::create(QStringLiteral("*")));
- additionalDependsItems.push_back(newDependsItem);
+ QStringList multiplexIds;
+ for (const ProductContext *dependency : dependencies) {
+ const QString depMultiplexId = dependency->multiplexConfigurationId;
+ if (productIsMultiplexed) { // (2)
+ dependsItem->setProperty(multiplexConfigurationIdsKey,
+ product.item->property(multiplexConfigurationIdKey));
+ break;
}
+ // (3b)
+ multiplexIds << depMultiplexId;
+ }
+ if (!multiplexIds.isEmpty()) {
+ dependsItem->setProperty(multiplexConfigurationIdsKey,
+ VariantValue::create(multiplexIds));
}
}
- for (Item * const newDependsItem : additionalDependsItems)
- Item::addChild(product.item, newDependsItem);
}
void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productItem)
{
checkCancelation();
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] prepareProduct " << productItem->file()->filePath();
+ qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath();
ProductContext productContext;
+ productContext.item = productItem;
+ productContext.project = projectContext;
productContext.name = m_evaluator->stringValue(productItem, QLatin1String("name"));
QBS_CHECK(!productContext.name.isEmpty());
bool profilePropertySet;
@@ -951,9 +945,15 @@ void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productI
QBS_CHECK(profilePropertySet);
const auto it = projectContext->result->profileConfigs.constFind(productContext.profileName);
if (it == projectContext->result->profileConfigs.constEnd()) {
+ const Profile profile(productContext.profileName, m_settings.get(), m_localProfiles);
+ if (!profile.exists()) {
+ ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()),
+ productItem->location());
+ handleProductError(error, &productContext);
+ return;
+ }
const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration(
- m_parameters.settingsDirectory(), productContext.profileName,
- m_parameters.configurationName());
+ profile, m_parameters.configurationName());
productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree(
buildConfig, m_parameters.overriddenValues());
projectContext->result->profileConfigs.insert(productContext.profileName,
@@ -961,8 +961,6 @@ void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productI
} else {
productContext.moduleProperties = it.value().toMap();
}
- productContext.item = productItem;
- productContext.project = projectContext;
initProductProperties(productContext);
ItemValuePtr itemValue = ItemValue::create(productItem);
@@ -982,16 +980,17 @@ void ModuleLoader::setupProductDependencies(ProductContext *productContext)
{
checkCancelation();
Item *item = productContext->item;
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] setupProductDependencies " << item->file()->filePath();
+ qCDebug(lcModuleLoader) << "setupProductDependencies" << productContext->name
+ << productContext->item->location();
QStringList extraSearchPaths = readExtraSearchPaths(item);
Settings settings(m_parameters.settingsDirectory());
const QVariantMap profileContents = productContext->project->result->profileConfigs
.value(productContext->profileName).toMap();
const QStringList prefsSearchPaths = Preferences(&settings, profileContents).searchPaths();
+ const QStringList &currentSearchPaths = m_reader->allSearchPaths();
for (const QString &p : prefsSearchPaths) {
- if (!m_moduleSearchPaths.contains(p) && FileInfo(p).exists())
+ if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
extraSearchPaths << p;
}
SearchPathsManager searchPathsManager(m_reader, extraSearchPaths);
@@ -1240,8 +1239,7 @@ void ModuleLoader::initProductProperties(const ProductContext &product)
void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *projectItem,
const Set<QString> &referencedFilePaths)
{
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] handleSubProject " << projectItem->file()->filePath();
+ qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath();
Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject);
bool subProjectEnabled = true;
@@ -1328,8 +1326,10 @@ QList<Item *> ModuleLoader::loadReferencedFile(const QString &relativePath,
subItem->setParent(dummyContext.project->item);
QList<Item *> loadedItems;
loadedItems << subItem;
- if (subItem->type() == ItemType::Product)
+ if (subItem->type() == ItemType::Product) {
+ handleProfileItems(subItem, dummyContext.project);
loadedItems << multiplexProductItem(&dummyContext, subItem);
+ }
return loadedItems;
}
@@ -1387,16 +1387,15 @@ void ModuleLoader::handlePropertyOptions(Item *optionsItem)
throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'")
.arg(name), optionsItem->location());
}
- // TODO: Uncomment in 1.10
-// if (property && decl.isExpired()) {
-// ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but "
-// "is still present.")
-// .arg(name).arg(removalVersion.toString()),
-// property->location());
-// e.append(Tr::tr("Removal version for '%1' specified here.").arg(name),
-// optionsItem->location());
-// m_logger.printWarning(e);
-// }
+ if (property && decl.isExpired()) {
+ ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but "
+ "is still present.")
+ .arg(name).arg(removalVersion.toString()),
+ property->location());
+ e.append(Tr::tr("Removal version for '%1' specified here.").arg(name),
+ optionsItem->location());
+ m_logger.printWarning(e);
+ }
optionsItem->parent()->setPropertyDeclaration(name, decl);
}
@@ -1615,6 +1614,97 @@ Item *ModuleLoader::loadItemFromFile(const QString &filePath)
return item;
}
+void ModuleLoader::handleProfileItems(Item *item, ProjectContext *projectContext)
+{
+ const std::vector<Item *> profileItems = collectProfileItems(item, projectContext);
+ for (Item * const profileItem : profileItems) {
+ try {
+ handleProfile(profileItem);
+ } catch (const ErrorInfo &e) {
+ handlePropertyError(e, m_parameters, m_logger);
+ }
+ }
+}
+
+std::vector<Item *> ModuleLoader::collectProfileItems(Item *item, ProjectContext *projectContext)
+{
+ QList<Item *> childItems = item->children();
+ std::vector<Item *> profileItems;
+ Item * scope = item->type() == ItemType::Project ? projectContext->scope : nullptr;
+ for (auto it = childItems.begin(); it != childItems.end();) {
+ Item * const childItem = *it;
+ if (childItem->type() == ItemType::Profile) {
+ if (!scope) {
+ const ItemValuePtr itemValue = ItemValue::create(item);
+ scope = Item::create(m_pool, ItemType::Scope);
+ scope->setProperty(QLatin1String("product"), itemValue);
+ scope->setFile(item->file());
+ scope->setScope(projectContext->scope);
+ }
+ childItem->setScope(scope);
+ profileItems.push_back(childItem);
+ it = childItems.erase(it);
+ } else {
+ if (childItem->type() == ItemType::Product) {
+ for (Item * const profileItem : collectProfileItems(childItem, projectContext))
+ profileItems.push_back(profileItem);
+ }
+ ++it;
+ }
+ }
+ if (!profileItems.empty())
+ item->setChildren(childItems);
+ return profileItems;
+}
+
+void ModuleLoader::evaluateProfileValues(const QualifiedId &namePrefix, Item *item,
+ Item *profileItem, QVariantMap &values)
+{
+ const Item::PropertyMap &props = item->properties();
+ for (auto it = props.begin(); it != props.end(); ++it) {
+ QualifiedId name = namePrefix;
+ name << it.key();
+ switch (it.value()->type()) {
+ case Value::ItemValueType:
+ evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(),
+ profileItem, values);
+ break;
+ case Value::VariantValueType:
+ values.insert(name.join(QLatin1Char('.')),
+ std::static_pointer_cast<VariantValue>(it.value())->value());
+ break;
+ case Value::JSSourceValueType:
+ item->setType(ItemType::ModulePrefix); // TODO: Introduce new item type
+ if (item != profileItem)
+ item->setScope(profileItem);
+ values.insert(name.join(QLatin1Char('.')),
+ m_evaluator->value(item, it.key()).toVariant());
+ break;
+ }
+ }
+}
+
+void ModuleLoader::handleProfile(Item *profileItem)
+{
+ QVariantMap values;
+ evaluateProfileValues(QualifiedId(), profileItem, profileItem, values);
+ const bool condition = values.take(QLatin1String("condition")).toBool();
+ if (!condition)
+ return;
+ const QString profileName = values.take(QLatin1String("name")).toString();
+ if (profileName.isEmpty())
+ throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location());
+ if (profileName == Profile::fallbackName()) {
+ throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.")
+ .arg(profileName), profileItem->location());
+ }
+ if (m_localProfiles.contains(profileName)) {
+ throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName),
+ profileItem->location());
+ }
+ m_localProfiles.insert(profileName, values);
+}
+
void ModuleLoader::collectProductsByName(const TopLevelProjectContext &topLevelProject)
{
for (ProjectContext * const project : topLevelProject.projects) {
@@ -1811,17 +1901,16 @@ void ModuleLoader::adjustDefiningItemsInGroupModuleInstances(const Item::Module
replacement->setScope(module.item);
}
QBS_CHECK(!replacement->hasOwnProperty(caseA));
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "[LDR] replacing defining item for prototype; module is "
- << module.name.toString() << module.item
- << ", property is " << propName
- << ", old defining item was " << v->definingItem()
- << " with scope" << v->definingItem()->scope()
- << ", new defining item is" << replacement
- << " with scope" << replacement->scope()
- << ", value source code is "
- << std::static_pointer_cast<JSSourceValue>(v)->sourceCode().toString();
- }
+ qCDebug(lcModuleLoader).noquote().nospace()
+ << "replacing defining item for prototype; module is "
+ << module.name.toString() << module.item
+ << ", property is " << propName
+ << ", old defining item was " << v->definingItem()
+ << " with scope" << v->definingItem()->scope()
+ << ", new defining item is" << replacement
+ << " with scope" << replacement->scope()
+ << ", value source code is "
+ << std::static_pointer_cast<JSSourceValue>(v)->sourceCode().toString();
replacement->setPropertyDeclaration(propName, decl);
replacement->setProperty(propName, v);
} else {
@@ -1853,11 +1942,9 @@ void ModuleLoader::adjustDefiningItemsInGroupModuleInstances(const Item::Module
replacement->scope()->setScope(depMod.item);
}
QBS_CHECK(!replacement->hasOwnProperty(caseA));
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "[LDR] reset instance scope of module "
- << depMod.name.toString() << " in property "
- << propName << " of module " << module.name;
- }
+ qCDebug(lcModuleLoader) << "reset instance scope of module"
+ << depMod.name.toString() << "in property"
+ << propName << "of module" << module.name;
}
QBS_CHECK(found);
}
@@ -1874,7 +1961,7 @@ void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *ite
// Resolve all Depends items.
ItemModuleList loadedModules;
QList<Item *> dependsItemPerLoadedModule;
- ProductDependencyResults productDependencies;
+ ProductDependencies productDependencies;
const auto &itemChildren = item->children();
for (Item * const child : itemChildren) {
if (child->type() != ItemType::Depends)
@@ -1910,7 +1997,9 @@ void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *ite
});
}
- dependsContext->productDependencies->append(productDependencies);
+ dependsContext->productDependencies->insert(
+ dependsContext->productDependencies->end(),
+ productDependencies.cbegin(), productDependencies.cend());
}
class RequiredChainManager
@@ -1930,12 +2019,11 @@ private:
void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *parentItem,
Item *dependsItem, ItemModuleList *moduleResults,
- ProductDependencyResults *productResults)
+ ProductDependencies *productResults)
{
checkCancelation();
if (!checkItemCondition(dependsItem)) {
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "Depends item disabled, ignoring.";
+ qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
return;
}
bool productTypesIsSet;
@@ -1958,7 +2046,7 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare
"exclusive."), dependsItem->location());
}
if (productTypes.isEmpty()) {
- m_logger.qbsTrace() << "Ignoring Depends item with empty productTypes list.";
+ qCDebug(lcModuleLoader) << "Ignoring Depends item with empty productTypes list.";
return;
}
@@ -1968,11 +2056,11 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare
dependency.productTypes = productTypes;
dependency.limitToSubProject
= m_evaluator->boolValue(dependsItem, QLatin1String("limitToSubProject"));
- productResults->append(dependency);
+ productResults->push_back(dependency);
return;
}
if (submodules.isEmpty() && submodulesPropertySet) {
- m_logger.qbsTrace() << "Ignoring Depends item with empty submodules list.";
+ qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list.";
return;
}
if (Q_UNLIKELY(submodules.count() > 1 && !dependsItem->id().isEmpty())) {
@@ -2014,11 +2102,10 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare
continue;
}
- RequiredChainManager requiredChainManager(m_requiredChain, isRequired);
-
QVariantMap defaultParameters;
- Item *moduleItem = loadModule(dependsContext->product, parentItem, dependsItem->location(),
- dependsItem->id(), moduleName, isRequired, &result.isProduct,
+ Item *moduleItem = loadModule(dependsContext->product, dependsContext->exportingProductItem,
+ parentItem, dependsItem->location(), dependsItem->id(),
+ moduleName, isRequired, &result.isProduct,
&defaultParameters);
if (!moduleItem) {
ErrorInfo e(Tr::tr("Dependency '%1' not found for product '%2'.")
@@ -2030,14 +2117,13 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare
}
throw e;
}
- if (result.isProduct && parentItem->type() == ItemType::Module) {
+ if (result.isProduct && !m_dependsChain.empty() && !m_dependsChain.back().isProduct) {
throw ErrorInfo(Tr::tr("Invalid dependency on product '%1': Modules cannot depend on "
"products. You may want to turn your module into a product and "
"add the dependency in that product's Export item.")
.arg(moduleName.toString()), dependsItem->location());
}
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "module loaded: " << moduleName.toString();
+ qCDebug(lcModuleLoader) << "module loaded:" << moduleName.toString();
result.name = moduleName;
result.item = moduleItem;
result.required = isRequired;
@@ -2045,22 +2131,31 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare
result.versionRange = versionRange;
moduleResults->append(result);
if (result.isProduct) {
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "product dependency loaded: " << moduleName.toString();
+ qCDebug(lcModuleLoader) << "product dependency loaded:" << moduleName.toString();
const QString profilesKey = QLatin1String("profiles");
- QStringList profiles = m_evaluator->stringListValue(dependsItem, profilesKey);
- if (profiles.isEmpty())
- profiles.append(QLatin1String("*"));
- const QString multiplexConfigurationId
- = m_evaluator->stringValue(dependsItem,
- QStringLiteral("multiplexConfigurationId"));
+ bool profilesPropertyWasSet = false;
+ QStringList profiles = m_evaluator->stringListValue(dependsItem, profilesKey,
+ &profilesPropertyWasSet);
+ if (profiles.isEmpty()) {
+ if (profilesPropertyWasSet)
+ profiles.append(QLatin1String("*"));
+ else
+ profiles.append(QString());
+ }
+ QStringList multiplexConfigurationIds
+ = m_evaluator->stringListValue(dependsItem,
+ QStringLiteral("multiplexConfigurationIds"));
+ if (multiplexConfigurationIds.isEmpty())
+ multiplexConfigurationIds << QString();
for (const QString &profile : qAsConst(profiles)) {
- ModuleLoaderResult::ProductInfo::Dependency dependency;
- dependency.name = moduleName.toString();
- dependency.profile = profile;
- dependency.multiplexConfigurationId = multiplexConfigurationId;
- dependency.isRequired = isRequired;
- productResults->append(dependency);
+ for (const QString &multiplexId : multiplexConfigurationIds) {
+ ModuleLoaderResult::ProductInfo::Dependency dependency;
+ dependency.name = moduleName.toString();
+ dependency.profile = profile;
+ dependency.multiplexConfigurationId = multiplexId;
+ dependency.isRequired = isRequired;
+ productResults->push_back(dependency);
+ }
}
}
}
@@ -2198,22 +2293,39 @@ Item *ModuleLoader::moduleInstanceItem(Item *containerItem, const QualifiedId &m
return instance;
}
-ModuleLoader::ProductModuleInfo ModuleLoader::loadProductModule(
- ModuleLoader::ProductContext *productContext, const QString &moduleName)
+ModuleLoader::ProductModuleInfo *ModuleLoader::productModule(ProductContext *productContext,
+ const QString &name)
+{
+ return &productContext->project->topLevelProject->productModules[name];
+}
+
+ModuleLoader::ProductModuleInfo *ModuleLoader::productModule(ProductContext *productContext,
+ const Item::Module &module)
+{
+ return module.isProduct ? productModule(productContext, module.name.toString()) : nullptr;
+}
+
+ModuleLoader::ProductContext *ModuleLoader::product(ProjectContext *projectContext,
+ const QString &name)
{
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] loadProductModule name: " << moduleName;
- ProductModuleInfo &pmi = productContext->project->topLevelProject->productModules[moduleName];
- if (pmi.exportItem && !pmi.dependenciesResolved) {
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] loadProductModule resolving dependencies.";
- DependsContext dependsContext;
- dependsContext.product = productContext;
- dependsContext.productDependencies = &pmi.productDependencies;
- resolveDependencies(&dependsContext, pmi.exportItem);
- pmi.dependenciesResolved = true;
+ auto itEnd = projectContext->products.end();
+ auto it = std::find_if(projectContext->products.begin(), itEnd,
+ [&name] (const ProductContext &ctx) {
+ return ctx.name == name;
+ });
+ return it == itEnd ? nullptr : &*it;
+}
+
+ModuleLoader::ProductContext *ModuleLoader::product(TopLevelProjectContext *tlpContext,
+ const QString &name)
+{
+ ProductContext *result = nullptr;
+ for (auto prj : tlpContext->projects) {
+ result = product(prj, name);
+ if (result)
+ break;
}
- return pmi;
+ return result;
}
class ModuleLoader::DependsChainManager
@@ -2225,17 +2337,17 @@ public:
{
const bool alreadyInChain = std::any_of(dependsChain.cbegin(), dependsChain.cend(),
[&module](const DependsChainEntry &e) {
- return e.first == module;
+ return e.name == module;
});
if (alreadyInChain) {
ErrorInfo error;
error.append(Tr::tr("Cyclic dependencies detected:"));
for (const DependsChainEntry &e : qAsConst(m_dependsChain))
- error.append(e.first.toString(), e.second);
+ error.append(e.name.toString(), e.location);
error.append(module.toString(), dependsLocation);
throw error;
}
- m_dependsChain.push_back(std::make_pair(module, dependsLocation));
+ m_dependsChain.emplace_back(module, dependsLocation);
}
~DependsChainManager() { m_dependsChain.pop_back(); }
@@ -2244,14 +2356,20 @@ private:
std::vector<DependsChainEntry> &m_dependsChain;
};
-Item *ModuleLoader::loadModule(ProductContext *productContext, Item *item,
- const CodeLocation &dependsItemLocation,
- const QString &moduleId, const QualifiedId &moduleName, bool isRequired,
- bool *isProductDependency, QVariantMap *defaultParameters)
+static bool isBaseModule(const QualifiedId &moduleName)
+{
+ return moduleName.count() == 1 && moduleName.first() == QLatin1String("qbs");
+}
+
+Item *ModuleLoader::loadModule(ProductContext *productContext, Item *exportingProductItem,
+ Item *item, const CodeLocation &dependsItemLocation,
+ const QString &moduleId, const QualifiedId &moduleName,
+ bool isRequired, bool *isProductDependency,
+ QVariantMap *defaultParameters)
{
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] loadModule name: " << moduleName << ", id: " << moduleId;
+ qCDebug(lcModuleLoader) << "loadModule name:" << moduleName.toString() << "id:" << moduleId;
+ RequiredChainManager requiredChainManager(m_requiredChain, isRequired);
DependsChainManager dependsChainManager(m_dependsChain, moduleName, dependsItemLocation);
Item *moduleInstance = moduleId.isEmpty()
@@ -2269,40 +2387,35 @@ Item *ModuleLoader::loadModule(ProductContext *productContext, Item *item,
QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance);
*isProductDependency = true;
- const ProductModuleInfo &pmi = loadProductModule(productContext, moduleName.toString());
- Item *modulePrototype = pmi.exportItem;
+ ProductModuleInfo *pmi = productModule(productContext, moduleName.toString());
+ Item *modulePrototype = pmi->exportItem;
if (modulePrototype) {
+ m_dependsChain.back().isProduct = true;
if (defaultParameters)
- *defaultParameters = pmi.defaultParameters;
+ *defaultParameters = pmi->defaultParameters;
} else {
+ pmi = nullptr;
*isProductDependency = false;
- QStringList moduleSearchPaths;
- for (const QString &searchPath : m_reader->searchPaths())
- addExtraModuleSearchPath(moduleSearchPaths, searchPath);
- bool cacheHit = false;
modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation,
- moduleName, moduleSearchPaths, isRequired, &cacheHit);
- static const QualifiedId baseModuleId = QualifiedId(QLatin1String("qbs"));
- if (modulePrototype && !cacheHit && moduleName == baseModuleId)
- setupBaseModulePrototype(modulePrototype);
+ moduleName, isRequired);
}
if (!modulePrototype)
return 0;
- instantiateModule(productContext, nullptr, item, moduleInstance, modulePrototype,
- moduleName, *isProductDependency);
+
+ instantiateModule(productContext, exportingProductItem, item, moduleInstance, modulePrototype,
+ moduleName, pmi);
return moduleInstance;
}
Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext,
const CodeLocation &dependsItemLocation, const QualifiedId &moduleName,
- const QStringList &extraSearchPaths, bool isRequired, bool *cacheHit)
+ bool isRequired)
{
- QStringList searchPaths = extraSearchPaths;
- searchPaths.append(m_moduleSearchPaths);
-
bool triedToLoadModule = false;
const QString fullName = moduleName.toString();
- for (const QString &path : qAsConst(searchPaths)) {
+ std::vector<Item *> candidates;
+ const QStringList &searchPaths = m_reader->allSearchPaths();
+ for (const QString &path : searchPaths) {
const QString dirPath = findExistingModulePath(path, moduleName);
if (dirPath.isEmpty())
continue;
@@ -2316,17 +2429,25 @@ Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext,
}
for (const QString &filePath : qAsConst(moduleFileNames)) {
triedToLoadModule = true;
- Item *module = loadModuleFile(productContext, fullName,
- moduleName.count() == 1
- && moduleName.first() == QLatin1String("qbs"),
- filePath, cacheHit, &triedToLoadModule);
+ Item *module = loadModuleFile(productContext, fullName, isBaseModule(moduleName),
+ filePath, &triedToLoadModule);
if (module)
- return module;
+ candidates.push_back(module);
if (!triedToLoadModule)
m_moduleDirListCache[dirPath].removeOne(filePath);
}
}
+ if (candidates.size() > 1) {
+ ErrorInfo e(Tr::tr("There is more than one candidate for module '%1'.").arg(fullName));
+ for (size_t i = 0; i < candidates.size(); ++i)
+ e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i)->location());
+ throw e;
+ }
+
+ if (candidates.size() == 1)
+ return candidates.at(0);
+
if (!isRequired)
return createNonPresentModule(fullName, QLatin1String("not found"), nullptr);
@@ -2385,29 +2506,25 @@ static QVariant convertToPropertyType(const QVariant &v, PropertyDeclaration::Ty
}
Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString &fullModuleName,
- bool isBaseModule, const QString &filePath, bool *cacheHit, bool *triedToLoad)
+ bool isBaseModule, const QString &filePath, bool *triedToLoad)
{
checkCancelation();
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] trying to load " << fullModuleName << " from " << filePath;
+ qCDebug(lcModuleLoader) << "trying to load" << fullModuleName << "from" << filePath;
const QString keyUniquifier = productContext->multiplexConfigurationId.isEmpty() ?
productContext->profileName : productContext->uniqueName();
const ModuleItemCache::key_type cacheKey(filePath, keyUniquifier);
const ItemCacheValue cacheValue = m_modulePrototypeItemCache.value(cacheKey);
if (cacheValue.module) {
- m_logger.qbsTrace() << "[LDR] loadModuleFile cache hit for " << filePath;
- *cacheHit = true;
+ qCDebug(lcModuleLoader) << "[LDR] loadModuleFile cache hit for" << filePath;
return cacheValue.enabled ? cacheValue.module : 0;
}
- *cacheHit = false;
Item * const module = loadItemFromFile(filePath);
if (module->type() != ItemType::Module) {
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "[MODLDR] Alleged module " << fullModuleName << " has type '"
- << module->typeName() << "', so it's not a module after all.";
- }
+ qCDebug(lcModuleLoader).nospace()
+ << "Alleged module " << fullModuleName << " has type '"
+ << module->typeName() << "', so it's not a module after all.";
*triedToLoad = false;
return 0;
}
@@ -2416,10 +2533,8 @@ Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString
module->setProperty(QLatin1String("name"), VariantValue::create(fullModuleName));
if (!isBaseModule) {
- DependsContext dependsContext;
- dependsContext.product = productContext;
- dependsContext.productDependencies = &productContext->info.usedProducts;
- resolveDependencies(&dependsContext, module);
+ // We need the base module for the Module.condition check below.
+ loadBaseModule(productContext, module);
}
// Module properties that are defined in the profile are used as default values.
@@ -2444,12 +2559,14 @@ Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString
// Check the condition last in case the condition needs to evaluate other properties that were
// set by the profile
if (!checkItemCondition(module)) {
- m_logger.qbsTrace() << "[LDR] module condition is false";
+ qCDebug(lcModuleLoader) << "[LDR] module condition is false";
m_modulePrototypeItemCache.insert(cacheKey, ItemCacheValue(module, false));
return 0;
}
- if (!isBaseModule)
+ if (isBaseModule)
+ setupBaseModulePrototype(module);
+ else
resolveParameterDeclarations(module);
for (const ErrorInfo &error : qAsConst(unknownProfilePropertyErrors))
@@ -2464,7 +2581,7 @@ Item::Module ModuleLoader::loadBaseModule(ProductContext *productContext, Item *
const QualifiedId baseModuleName(QLatin1String("qbs"));
Item::Module baseModuleDesc;
baseModuleDesc.name = baseModuleName;
- baseModuleDesc.item = loadModule(productContext, item, CodeLocation(), QString(),
+ baseModuleDesc.item = loadModule(productContext, nullptr, item, CodeLocation(), QString(),
baseModuleName, true, &baseModuleDesc.isProduct, nullptr);
if (productContext->item) {
const Item * const qbsInstanceItem
@@ -2481,134 +2598,14 @@ Item::Module ModuleLoader::loadBaseModule(ProductContext *productContext, Item *
return baseModuleDesc;
}
-static QStringList hostOS()
-{
- QStringList hostSystem;
-
-#if defined(Q_OS_AIX)
- hostSystem << QLatin1String("aix");
-#endif
-#if defined(Q_OS_ANDROID)
- hostSystem << QLatin1String("android");
-#endif
-#if defined(Q_OS_BLACKBERRY)
- hostSystem << QLatin1String("blackberry");
-#endif
-#if defined(Q_OS_BSD4)
- hostSystem << QLatin1String("bsd") << QLatin1String("bsd4");
-#endif
-#if defined(Q_OS_BSDI)
- hostSystem << QLatin1String("bsdi");
-#endif
-#if defined(Q_OS_CYGWIN)
- hostSystem << QLatin1String("cygwin");
-#endif
-#if defined(Q_OS_DARWIN)
- hostSystem << QLatin1String("darwin");
-#endif
-#if defined(Q_OS_DGUX)
- hostSystem << QLatin1String("dgux");
-#endif
-#if defined(Q_OS_DYNIX)
- hostSystem << QLatin1String("dynix");
-#endif
-#if defined(Q_OS_FREEBSD)
- hostSystem << QLatin1String("freebsd");
-#endif
-#if defined(Q_OS_HPUX)
- hostSystem << QLatin1String("hpux");
-#endif
-#if defined(Q_OS_HURD)
- hostSystem << QLatin1String("hurd");
-#endif
-#if defined(Q_OS_INTEGRITY)
- hostSystem << QLatin1String("integrity");
-#endif
-#if defined(Q_OS_IOS)
- hostSystem << QLatin1String("ios");
-#endif
-#if defined(Q_OS_IRIX)
- hostSystem << QLatin1String("irix");
-#endif
-#if defined(Q_OS_LINUX)
- hostSystem << QLatin1String("linux");
-#endif
-#if defined(Q_OS_LYNX)
- hostSystem << QLatin1String("lynx");
-#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_OSX)
- hostSystem << QLatin1String("macos") << QLatin1String("osx");
-#endif
-#if defined(Q_OS_MSDOS)
- hostSystem << QLatin1String("msdos");
-#endif
-#if defined(Q_OS_NACL)
- hostSystem << QLatin1String("nacl");
-#endif
-#if defined(Q_OS_NETBSD)
- hostSystem << QLatin1String("netbsd");
-#endif
-#if defined(Q_OS_OPENBSD)
- hostSystem << QLatin1String("openbsd");
-#endif
-#if defined(Q_OS_OS2)
- hostSystem << QLatin1String("os2");
-#endif
-#if defined(Q_OS_OS2EMX)
- hostSystem << QLatin1String("os2emx");
-#endif
-#if defined(Q_OS_OSF)
- hostSystem << QLatin1String("osf");
-#endif
-#if defined(Q_OS_QNX)
- hostSystem << QLatin1String("qnx");
-#endif
-#if defined(Q_OS_ONX6)
- hostSystem << QLatin1String("qnx6");
-#endif
-#if defined(Q_OS_RELIANT)
- hostSystem << QLatin1String("reliant");
-#endif
-#if defined(Q_OS_SCO)
- hostSystem << QLatin1String("sco");
-#endif
-#if defined(Q_OS_SOLARIS)
- hostSystem << QLatin1String("solaris");
-#endif
-#if defined(Q_OS_SYMBIAN)
- hostSystem << QLatin1String("symbian");
-#endif
-#if defined(Q_OS_ULTRIX)
- hostSystem << QLatin1String("ultrix");
-#endif
-#if defined(Q_OS_UNIX)
- hostSystem << QLatin1String("unix");
-#endif
-#if defined(Q_OS_UNIXWARE)
- hostSystem << QLatin1String("unixware");
-#endif
-#if defined(Q_OS_VXWORKS)
- hostSystem << QLatin1String("vxworks");
-#endif
-#if defined(Q_OS_WIN32)
- hostSystem << QLatin1String("windows");
-#endif
-#if defined(Q_OS_WINCE)
- hostSystem << QLatin1String("windowsce");
-#endif
-#if defined(Q_OS_WINPHONE)
- hostSystem << QLatin1String("windowsphone");
-#endif
-#if defined(Q_OS_WINRT)
- hostSystem << QLatin1String("winrt");
-#endif
-
- return hostSystem;
-}
-
void ModuleLoader::setupBaseModulePrototype(Item *prototype)
{
- prototype->setProperty(QLatin1String("hostOS"), VariantValue::create(hostOS()));
+ prototype->setProperty(QLatin1String("hostOS"), [] {
+ QStringList list;
+ for (const auto &s : HostOsInfo::hostOSIdentifiers())
+ list.push_back(QString::fromStdString(s));
+ return VariantValue::create(list);
+ }());
prototype->setProperty(QLatin1String("libexecPath"),
VariantValue::create(m_parameters.libexecPath()));
@@ -2636,12 +2633,51 @@ static QList<Item *> collectItemsWithId(Item *item)
return result;
}
+static std::vector<std::pair<QualifiedId, ItemValuePtr>> instanceItemProperties(Item *item)
+{
+ std::vector<std::pair<QualifiedId, ItemValuePtr>> result;
+ QualifiedId name;
+ std::function<void(Item *)> f = [&] (Item *item) {
+ for (auto it = item->properties().begin(); it != item->properties().end(); ++it) {
+ if (it.value()->type() != Value::ItemValueType)
+ continue;
+ ItemValuePtr itemValue = std::static_pointer_cast<ItemValue>(it.value());
+ if (!itemValue->item())
+ continue;
+ name.append(it.key());
+ if (itemValue->item()->type() == ItemType::ModulePrefix)
+ f(itemValue->item());
+ else
+ result.push_back(std::make_pair(name, itemValue));
+ name.removeLast();
+ }
+ };
+ f(item);
+ return result;
+}
+
void ModuleLoader::instantiateModule(ProductContext *productContext, Item *exportingProduct,
Item *instanceScope, Item *moduleInstance, Item *modulePrototype,
- const QualifiedId &moduleName, bool isProduct)
+ const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo)
{
+ Item *deepestModuleInstance = moduleInstance;
+ while (deepestModuleInstance->prototype()
+ && deepestModuleInstance->prototype()->type() == ItemType::ModuleInstance) {
+ deepestModuleInstance = deepestModuleInstance->prototype();
+ }
+ deepestModuleInstance->setPrototype(modulePrototype);
const QString fullName = moduleName.toString();
- moduleInstance->setPrototype(modulePrototype);
+ const QString generalOverrideKey = QLatin1String("modules.") + fullName;
+ const QString perProductOverrideKey = QLatin1String("products.") + productContext->name
+ + QLatin1Char('.') + fullName;
+ for (Item *instance = moduleInstance; instance; instance = instance->prototype()) {
+ overrideItemProperties(instance, generalOverrideKey, m_parameters.overriddenValuesTree());
+ overrideItemProperties(instance, perProductOverrideKey,
+ m_parameters.overriddenValuesTree());
+ if (instance == deepestModuleInstance)
+ break;
+ }
+
moduleInstance->setFile(modulePrototype->file());
moduleInstance->setLocation(modulePrototype->location());
QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance);
@@ -2656,16 +2692,10 @@ void ModuleLoader::instantiateModule(ProductContext *productContext, Item *expor
if (productContext->scope)
productContext->scope->copyProperty(QLatin1String("product"), moduleScope);
else
- QBS_CHECK(moduleName.toString() == QLatin1String("qbs")); // Dummy product.
+ QBS_CHECK(fullName == QLatin1String("qbs")); // Dummy product.
- if (isProduct) {
- exportingProduct = 0;
- for (Item *exportItem = modulePrototype; exportItem && !exportingProduct;
- exportItem = exportItem->prototype()) {
- // exportItem is either of type ModuleInstance or Export. Only the latter has
- // a parent item, which is always of type Product.
- exportingProduct = exportItem->parent();
- }
+ if (productModuleInfo) {
+ exportingProduct = productModuleInfo->exportItem->parent();
QBS_CHECK(exportingProduct);
QBS_CHECK(exportingProduct->type() == ItemType::Product);
}
@@ -2686,7 +2716,6 @@ void ModuleLoader::instantiateModule(ProductContext *productContext, Item *expor
ValuePtr v = exportingProduct->property(QLatin1String("sourceDirectory"))->clone();
moduleInstance->setProperty(pd.name(), v);
}
-
moduleInstance->setScope(moduleScope);
QHash<Item *, Item *> prototypeInstanceMap;
@@ -2706,24 +2735,33 @@ void ModuleLoader::instantiateModule(ProductContext *productContext, Item *expor
}
}
- // create module instances for the dependencies of this module
- for (Item::Module m : modulePrototype->modules()) {
- Item *depinst = moduleInstanceItem(moduleInstance, m.name);
- const bool safetyCheck = true;
- if (safetyCheck) {
- Item *obj = moduleInstance;
- for (int i = 0; i < m.name.count(); ++i) {
- ItemValuePtr iv = obj->itemProperty(m.name.at(i));
- QBS_CHECK(iv);
- obj = iv->item();
- }
- QBS_CHECK(obj == depinst);
+ // For foo.bar in modulePrototype create an item foo in moduleInstance.
+ for (auto iip : instanceItemProperties(modulePrototype)) {
+ if (iip.second->item()->properties().isEmpty())
+ continue;
+ qCDebug(lcModuleLoader) << "The prototype of " << moduleName
+ << " sets properties on " << iip.first.toString();
+ Item *item = moduleInstanceItem(moduleInstance, iip.first);
+ item->setPrototype(iip.second->item());
+ if (iip.second->createdByPropertiesBlock()) {
+ ItemValuePtr itemValue = moduleInstance->itemProperty(iip.first.first());
+ for (int i = 1; i < iip.first.size(); ++i)
+ itemValue = itemValue->item()->itemProperty(iip.first.at(i));
+ itemValue->setCreatedByPropertiesBlock(true);
}
- QBS_ASSERT(depinst != m.item, continue);
- instantiateModule(productContext, isProduct ? exportingProduct : nullptr, moduleInstance,
- depinst, m.item, m.name, m.isProduct);
- m.item = depinst;
- moduleInstance->addModule(m);
+ }
+
+ // Resolve dependencies of this module instance.
+ DependsContext dependsContext;
+ dependsContext.product = productContext;
+ dependsContext.exportingProductItem = exportingProduct;
+ QBS_ASSERT(moduleInstance->modules().empty(), moduleInstance->removeModules());
+ if (productModuleInfo) {
+ dependsContext.productDependencies = &productContext->productModuleDependencies[fullName];
+ resolveDependencies(&dependsContext, moduleInstance);
+ } else if (!isBaseModule(moduleName)) {
+ dependsContext.productDependencies = &productContext->info.usedProducts;
+ resolveDependencies(&dependsContext, moduleInstance);
}
// Check readonly properties.
@@ -2735,13 +2773,6 @@ void ModuleLoader::instantiateModule(ProductContext *productContext, Item *expor
throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()),
moduleInstance->property(pd.name())->location());
}
-
- const QString generalverrideKey = QLatin1String("modules.") + fullName;
- overrideItemProperties(moduleInstance, generalverrideKey, m_parameters.overriddenValuesTree());
- const QString perProductOverrideKey = QLatin1String("products.") + productContext->name
- + QLatin1Char('.') + fullName;
- overrideItemProperties(moduleInstance, perProductOverrideKey,
- m_parameters.overriddenValuesTree());
}
void ModuleLoader::createChildInstances(Item *instance, Item *prototype,
@@ -2770,7 +2801,7 @@ void ModuleLoader::resolveProbes(ProductContext *productContext, Item *item)
void ModuleLoader::resolveProbe(ProductContext *productContext, Item *parent, Item *probe)
{
- m_logger.qbsTrace() << "Resolving Probe at " << probe->location().toString();
+ qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString();
const QString &probeId = probeGlobalId(probe);
if (Q_UNLIKELY(probeId.isEmpty()))
throw ErrorInfo(Tr::tr("Probe.id must be set."), probe->location());
@@ -2816,7 +2847,7 @@ void ModuleLoader::resolveProbe(ProductContext *productContext, Item *parent, It
resolvedProbe = findCurrentProbe(probe->location(), condition, initialProperties);
ErrorInfo evalError;
if (!condition) {
- m_logger.qbsDebug() << "Probe disabled; skipping";
+ qCDebug(lcModuleLoader) << "Probe disabled; skipping";
} else if (!resolvedProbe) {
QScriptValue sv = engine->evaluate(configureScript->sourceCodeForEvaluation());
if (Q_UNLIKELY(engine->hasErrorOrException(sv)))
@@ -2932,7 +2963,7 @@ Item *ModuleLoader::wrapInProjectIfNecessary(Item *item)
QString ModuleLoader::findExistingModulePath(const QString &searchPath,
const QualifiedId &moduleName)
{
- QString dirPath = searchPath;
+ QString dirPath = searchPath + QStringLiteral("/modules");
for (const QString &moduleNamePart : moduleName) {
dirPath = FileInfo::resolvePath(dirPath, moduleNamePart);
if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath))
@@ -3013,8 +3044,7 @@ static std::vector<Item::Module> allModules(Item *item)
void ModuleLoader::addProductModuleDependencies(ProductContext *productContext,
const Item::Module &module)
{
- auto deps = productContext->project->topLevelProject->productModules.value(
- module.name.toString()).productDependencies;
+ auto deps = productContext->productModuleDependencies.at(module.name.toString());
const QString multiplexConfigurationIdKey = QStringLiteral("multiplexConfigurationId");
QList<ModuleLoaderResult::ProductInfo::Dependency> additionalDependencies;
const bool productIsMultiplexed = !productContext->multiplexConfigurationId.isEmpty();
@@ -3050,19 +3080,19 @@ void ModuleLoader::addProductModuleDependencies(ProductContext *productContext,
ModuleLoaderResult::ProductInfo::Dependency newDependency = dep;
newDependency.multiplexConfigurationId
= dependencies.at(i)->multiplexConfigurationId;
- newDependency.profile = QLatin1String("*");
additionalDependencies << newDependency;
}
}
}
- productContext->info.usedProducts.append(deps);
- productContext->info.usedProducts.append(additionalDependencies);
+ productContext->info.usedProducts.insert(productContext->info.usedProducts.end(),
+ deps.cbegin(), deps.cend());
+ productContext->info.usedProducts.insert(productContext->info.usedProducts.end(),
+ additionalDependencies.cbegin(), additionalDependencies.cend());
}
void ModuleLoader::addTransitiveDependencies(ProductContext *ctx)
{
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[MODLDR] addTransitiveDependencies";
+ qCDebug(lcModuleLoader) << "addTransitiveDependencies";
std::vector<Item::Module> transitiveDeps = allModules(ctx->item);
std::sort(transitiveDeps.begin(), transitiveDeps.end());
@@ -3080,8 +3110,8 @@ void ModuleLoader::addTransitiveDependencies(ProductContext *ctx)
addProductModuleDependencies(ctx, module);
} else {
Item::Module dep;
- dep.item = loadModule(ctx, ctx->item, ctx->item->location(), QString(), module.name,
- module.required, &dep.isProduct, &dep.parameters);
+ dep.item = loadModule(ctx, nullptr, ctx->item, ctx->item->location(), QString(),
+ module.name, module.required, &dep.isProduct, &dep.parameters);
if (!dep.item) {
throw ErrorInfo(Tr::tr("Module '%1' not found when setting up transitive "
"dependencies for product '%2'.").arg(module.name.toString(),
@@ -3098,10 +3128,8 @@ void ModuleLoader::addTransitiveDependencies(ProductContext *ctx)
Item *ModuleLoader::createNonPresentModule(const QString &name, const QString &reason, Item *module)
{
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace() << "Non-required module '" << name << "' not loaded (" << reason << ")."
- << "Creating dummy module for presence check.";
- }
+ qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")."
+ << "Creating dummy module for presence check.";
if (!module) {
module = Item::create(m_pool, ItemType::ModuleInstance);
module->setFile(FileContext::create());
diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h
index aeea34ad0..b6c97f3da 100644
--- a/src/lib/corelib/language/moduleloader.h
+++ b/src/lib/corelib/language/moduleloader.h
@@ -54,6 +54,7 @@
#include <QtCore/qvariant.h>
#include <map>
+#include <memory>
#include <stack>
#include <unordered_map>
#include <vector>
@@ -61,6 +62,7 @@
namespace qbs {
class CodeLocation;
+class Settings;
namespace Internal {
@@ -94,7 +96,7 @@ struct ModuleLoaderResult
};
QList<ProbeConstPtr> probes;
- QList<Dependency> usedProducts;
+ std::vector<Dependency> usedProducts;
ModulePropertiesPerGroup modulePropertiesSetInGroups;
ErrorInfo delayedError;
};
@@ -152,6 +154,8 @@ private:
class ProjectContext;
+ typedef std::vector<ModuleLoaderResult::ProductInfo::Dependency> ProductDependencies;
+
class ProductContext : public ContextBase
{
public:
@@ -161,6 +165,7 @@ private:
QString profileName;
QString multiplexConfigurationId;
QVariantMap moduleProperties;
+ std::map<QString, ProductDependencies> productModuleDependencies;
QString uniqueName() const;
};
@@ -179,8 +184,6 @@ private:
struct ProductModuleInfo
{
Item *exportItem = nullptr;
- bool dependenciesResolved = false;
- QList<ModuleLoaderResult::ProductInfo::Dependency> productDependencies;
QVariantMap defaultParameters;
};
@@ -200,12 +203,11 @@ private:
class DependsContext
{
public:
- ProductContext *product;
- QList<ModuleLoaderResult::ProductInfo::Dependency> *productDependencies;
+ ProductContext *product = nullptr;
+ Item *exportingProductItem = nullptr;
+ ProductDependencies *productDependencies;
};
- typedef QList<ModuleLoaderResult::ProductInfo::Dependency> ProductDependencyResults;
-
void handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem,
const QString &buildDirectory, const Set<QString> &referencedFilePaths);
void handleProject(ModuleLoaderResult *loadResult,
@@ -222,7 +224,6 @@ private:
bool aggregate = false;
VariantValuePtr multiplexedType;
- static QString configurationStringFromId(const QString &idString);
QString toIdString(size_t row) const;
};
@@ -264,28 +265,32 @@ private:
void resolveDependencies(DependsContext *dependsContext, Item *item);
class ItemModuleList;
void resolveDependsItem(DependsContext *dependsContext, Item *parentItem, Item *dependsItem,
- ItemModuleList *moduleResults, ProductDependencyResults *productResults);
+ ItemModuleList *moduleResults, ProductDependencies *productResults);
void forwardParameterDeclarations(const Item *dependsItem, const ItemModuleList &modules);
void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
const ItemModuleList &modules);
void resolveParameterDeclarations(const Item *module);
QVariantMap extractParameters(Item *dependsItem) const;
Item *moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName);
- ProductModuleInfo loadProductModule(ProductContext *productContext, const QString &moduleName);
- Item *loadModule(ProductContext *productContext, Item *item,
+ static ProductModuleInfo *productModule(ProductContext *productContext, const QString &name);
+ static ProductModuleInfo *productModule(ProductContext *productContext,
+ const Item::Module &module);
+ static ProductContext *product(ProjectContext *projectContext, const QString &name);
+ static ProductContext *product(TopLevelProjectContext *tlpContext, const QString &name);
+ Item *loadModule(ProductContext *productContext, Item *exportingProductItem, Item *item,
const CodeLocation &dependsItemLocation, const QString &moduleId,
const QualifiedId &moduleName, bool isRequired, bool *isProductDependency,
QVariantMap *defaultParameters);
Item *searchAndLoadModuleFile(ProductContext *productContext,
const CodeLocation &dependsItemLocation, const QualifiedId &moduleName,
- const QStringList &extraSearchPaths, bool isRequired, bool *cacheHit);
+ bool isRequired);
Item *loadModuleFile(ProductContext *productContext, const QString &fullModuleName,
- bool isBaseModule, const QString &filePath, bool *cacheHit, bool *triedToLoad);
+ bool isBaseModule, const QString &filePath, bool *triedToLoad);
Item::Module loadBaseModule(ProductContext *productContext, Item *item);
void setupBaseModulePrototype(Item *prototype);
void instantiateModule(ProductContext *productContext, Item *exportingProductItem,
Item *instanceScope, Item *moduleInstance, Item *modulePrototype,
- const QualifiedId &moduleName, bool isProduct);
+ const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo);
void createChildInstances(Item *instance, Item *prototype,
QHash<Item *, Item *> *prototypeInstanceMap) const;
void resolveProbes(ProductContext *productContext, Item *item);
@@ -323,19 +328,34 @@ private:
Item *loadItemFromFile(const QString &filePath);
void collectProductsByName(const TopLevelProjectContext &topLevelProject);
+ void handleProfileItems(Item *item, ProjectContext *projectContext);
+ std::vector<Item *> collectProfileItems(Item *item, ProjectContext *projectContext);
+ void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem,
+ QVariantMap &values);
+ void handleProfile(Item *profileItem);
+
ItemPool *m_pool;
Logger &m_logger;
ProgressObserver *m_progressObserver;
ItemReader *m_reader;
Evaluator *m_evaluator;
- QStringList m_moduleSearchPaths;
QMap<QString, QStringList> m_moduleDirListCache;
ModuleItemCache m_modulePrototypeItemCache;
QHash<const Item *, Item::PropertyDeclarationMap> m_parameterDeclarations;
Set<Item *> m_disabledItems;
std::vector<bool> m_requiredChain;
- using DependsChainEntry = std::pair<QualifiedId, CodeLocation>;
+ struct DependsChainEntry
+ {
+ DependsChainEntry(const QualifiedId &name, const CodeLocation &location)
+ : name(name), location(location)
+ {
+ }
+
+ QualifiedId name;
+ CodeLocation location;
+ bool isProduct = false;
+ };
class DependsChainManager;
std::vector<DependsChainEntry> m_dependsChain;
@@ -343,8 +363,10 @@ private:
QHash<QString, QList<ProbeConstPtr>> m_oldProductProbes;
QHash<CodeLocation, QList<ProbeConstPtr>> m_currentProbes;
QVariantMap m_storedProfiles;
+ QVariantMap m_localProfiles;
std::multimap<QString, const ProductContext *> m_productsByName;
SetupProjectParameters m_parameters;
+ std::unique_ptr<Settings> m_settings;
Version m_qbsVersion;
Item *m_tempScopeItem = nullptr;
qint64 m_elapsedTimeProbes;
diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp
index 0b81f6f4e..79945f1a4 100644
--- a/src/lib/corelib/language/projectresolver.cpp
+++ b/src/lib/corelib/language/projectresolver.cpp
@@ -50,6 +50,7 @@
#include "value.h"
#include <jsextensions/moduleproperties.h>
+#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
@@ -88,7 +89,6 @@ struct ProjectResolver::ProductContext
{
ResolvedProductPtr product;
QString buildDirectory;
- FileTags additionalFileTags;
Item *item;
typedef std::pair<ArtifactPropertiesPtr, CodeLocation> ArtifactPropertiesInfo;
QHash<QStringList, ArtifactPropertiesInfo> artifactPropertiesPerFilter;
@@ -146,8 +146,7 @@ TopLevelProjectPtr ProjectResolver::resolve()
{
TimedActivityLogger projectResolverTimer(m_logger, Tr::tr("ProjectResolver"),
m_setupParams.logElapsedTime());
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[PR] resolving " << m_loadResult.root->file()->filePath();
+ qCDebug(lcProjectResolver) << "resolving" << m_loadResult.root->file()->filePath();
m_productContext = 0;
m_moduleContext = 0;
@@ -251,7 +250,6 @@ TopLevelProjectPtr ProjectResolver::resolveTopLevelProject()
resolveProject(m_loadResult.root, &projectContext);
project->setBuildConfiguration(m_setupParams.finalBuildConfigurationTree());
project->overriddenValues = m_setupParams.overriddenValues();
- project->usedEnvironment = m_engine->usedEnvironment();
project->canonicalFilePathResults = m_engine->canonicalFilePathResults();
project->fileExistsResults = m_engine->fileExistsResults();
project->directoryEntriesResults = m_engine->directoryEntriesResults();
@@ -368,7 +366,7 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext)
QBS_CHECK(!product->profile.isEmpty());
product->multiplexConfigurationId
= m_evaluator->stringValue(item, QLatin1String("multiplexConfigurationId"));
- m_logger.qbsTrace() << "[PR] resolveProduct " << product->uniqueName();
+ qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName();
m_productsByName.insert(product->uniqueName(), product);
product->enabled = m_evaluator->boolValue(item, QLatin1String("condition"));
ModuleLoaderResult::ProductInfo &pi = m_loadResult.productInfos[item];
@@ -392,7 +390,7 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext)
pi.delayedError.clear();
return;
}
- product->fileTags = m_evaluator->fileTagsValue(item, QLatin1String("type"));
+ gatherProductTypes(product.get(), item);
product->targetName = m_evaluator->stringValue(item, QLatin1String("targetName"));
product->sourceDirectory = m_evaluator->stringValue(item, QLatin1String("sourceDirectory"));
const QString destDirKey = QLatin1String("destinationDirectory");
@@ -444,7 +442,6 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext)
callItemFunction(mapping, child, projectContext);
resolveModules(item, projectContext);
- product->fileTags += productContext.additionalFileTags;
for (const FileTag &t : qAsConst(product->fileTags))
m_productsByType[t] << product;
@@ -456,25 +453,8 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext)
void ProjectResolver::resolveModules(const Item *item, ProjectContext *projectContext)
{
- // Breadth first search needed here, because the product might set properties on the cpp module,
- // whose children must be evaluated in that context then.
- std::queue<Item::Module> modules;
for (const Item::Module &m : item->modules())
- modules.push(m);
- Set<QualifiedId> seen;
- while (!modules.empty()) {
- const Item::Module m = modules.front();
- modules.pop();
- if (!seen.insert(m.name).second)
- continue;
resolveModule(m.name, m.item, m.isProduct, m.parameters, projectContext);
- for (const Item::Module &childModule : m.item->modules())
- modules.push(childModule);
- }
- std::sort(m_productContext->product->modules.begin(), m_productContext->product->modules.end(),
- [](const ResolvedModuleConstPtr &m1, const ResolvedModuleConstPtr &m2) {
- return m1->name < m2->name;
- });
}
void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
@@ -497,9 +477,6 @@ void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, b
module->setupRunEnvironmentScript = scriptFunctionValue(item,
QLatin1String("setupRunEnvironment"));
- m_productContext->additionalFileTags +=
- m_evaluator->fileTagsValue(item, QLatin1String("additionalProductTypes"));
-
for (const Item::Module &m : item->modules()) {
if (m_evaluator->boolValue(m.item, QLatin1String("present")))
module->moduleDependencies += m.name.toString();
@@ -525,6 +502,19 @@ void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, b
m_moduleContext = oldModuleContext;
}
+void ProjectResolver::gatherProductTypes(ResolvedProduct *product, Item *item)
+{
+ product->fileTags = m_evaluator->fileTagsValue(item, QLatin1String("type"));
+ for (const Item::Module &m : item->modules()) {
+ if (m.item->isPresentModule()) {
+ product->fileTags += m_evaluator->fileTagsValue(m.item,
+ QLatin1String("additionalProductTypes"));
+ }
+ }
+ item->setProperty(QLatin1String("type"),
+ VariantValue::create(product->fileTags.toStringList()));
+}
+
SourceArtifactPtr ProjectResolver::createSourceArtifact(const ResolvedProductPtr &rproduct,
const QString &fileName, const GroupPtr &group, bool wildcard,
const CodeLocation &filesLocation, QHash<QString, CodeLocation> *fileLocations,
@@ -593,13 +583,13 @@ QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group
// Step 3: Evaluate all these properties and replace their values in the map
QVariantMap modulesMap = currentValues.value(QLatin1String("modules")).toMap();
QHash<QString, QStringList> propsPerModule;
- for (auto it = propsToEval.cbegin(); it != propsToEval.cend(); ++it) {
- const QualifiedId fullPropName = *it;
+ for (auto fullPropName : propsToEval) {
const QString moduleName
= QualifiedId(fullPropName.mid(0, fullPropName.count() - 1)).toString();
propsPerModule[moduleName] << fullPropName.last();
}
EvalCacheEnabler cachingEnabler(m_evaluator);
+ m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory);
for (const Item::Module &module : group->modules()) {
const QString &fullModName = module.name.toString();
const QStringList propsForModule = propsPerModule.take(fullModName);
@@ -611,6 +601,7 @@ QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group
modulesMap.insert(fullModName,
evaluateProperties(module.item, module.item, reusableValues, true));
}
+ m_evaluator->clearPathPropertiesBaseDir();
QVariantMap newValues = currentValues;
newValues.insert(QLatin1String("modules"), modulesMap);
return newValues;
@@ -637,6 +628,9 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext)
if (m_productContext->currentGroup)
isEnabled = isEnabled && m_productContext->currentGroup->enabled;
QStringList files = m_evaluator->stringListValue(item, QLatin1String("files"));
+ bool fileTagsSet;
+ const FileTags fileTags = m_evaluator->fileTagsValue(item, QLatin1String("fileTags"),
+ &fileTagsSet);
const QStringList fileTagsFilter
= m_evaluator->stringListValue(item, QLatin1String("fileTagsFilter"));
if (!fileTagsFilter.isEmpty()) {
@@ -661,6 +655,7 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext)
return;
ArtifactPropertiesPtr aprops = ArtifactProperties::create();
aprops->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter));
+ aprops->setExtraFileTags(fileTags);
aprops->setPropertyMapInternal(moduleProperties);
m_productContext->product->artifactProperties += aprops;
m_productContext->artifactPropertiesPerFilter.insert(fileTagsFilter,
@@ -673,7 +668,11 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext)
patterns.append(files.takeAt(i));
}
GroupPtr group = ResolvedGroup::create();
- group->prefix = m_evaluator->stringValue(item, QLatin1String("prefix"));
+ bool prefixWasSet = false;
+ group->prefix = m_evaluator->stringValue(item, QLatin1String("prefix"), QString(),
+ &prefixWasSet);
+ if (!prefixWasSet && m_productContext->currentGroup)
+ group->prefix = m_productContext->currentGroup->prefix;
if (!group->prefix.isEmpty()) {
for (int i = files.count(); --i >= 0;)
files[i].prepend(group->prefix);
@@ -681,8 +680,7 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext)
group->location = item->location();
group->enabled = isEnabled;
group->properties = moduleProperties;
- bool fileTagsSet;
- group->fileTags = m_evaluator->fileTagsValue(item, QLatin1String("fileTags"), &fileTagsSet);
+ group->fileTags = fileTags;
group->overrideTags = m_evaluator->boolValue(item, QLatin1String("overrideTags"));
if (group->overrideTags && fileTagsSet) {
if (group->fileTags.isEmpty() )
@@ -694,12 +692,12 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext)
const CodeLocation filesLocation = item->property(QLatin1String("files"))->location();
ErrorInfo fileError;
if (!patterns.isEmpty()) {
- SourceWildCards::Ptr wildcards = SourceWildCards::create();
+ group->wildcards = std::unique_ptr<SourceWildCards>(new SourceWildCards);
+ SourceWildCards *wildcards = group->wildcards.get();
+ wildcards->group = group.get();
wildcards->excludePatterns = m_evaluator->stringListValue(item,
QLatin1String("excludeFiles"));
- wildcards->prefix = group->prefix;
wildcards->patterns = patterns;
- group->wildcards = wildcards;
const Set<QString> files = wildcards->expandPatterns(group,
FileInfo::path(item->file()->filePath()),
projectContext->project->topLevelProject()->buildDirectory);
@@ -718,7 +716,7 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext)
throw ErrorInfo(fileError);
m_logger.printWarning(fileError);
} else {
- m_logger.qbsDebug() << fileError.toString();
+ qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString();
}
}
group->name = m_evaluator->stringValue(item, QLatin1String("name"));
@@ -863,10 +861,12 @@ void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext)
"does not declare any input tags."), item->location()));
return;
}
- if (m_productContext)
+ if (m_productContext) {
+ rule->product = m_productContext->product.get();
m_productContext->product->rules += rule;
- else
+ } else {
projectContext->rules += rule;
+ }
}
void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item)
@@ -945,14 +945,16 @@ void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectConte
if (pattern.isEmpty())
throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location());
}
- fileTaggers += FileTagger::create(patterns, fileTags);
+
+ const int priority = m_evaluator->intValue(item, QLatin1String("priority"));
+ fileTaggers += FileTagger::create(patterns, fileTags, priority);
}
void ProjectResolver::resolveScanner(Item *item, ProjectResolver::ProjectContext *projectContext)
{
checkCancelation();
if (!m_evaluator->boolValue(item, QLatin1String("condition"))) {
- m_logger.qbsTrace() << "[PR] scanner condition is false";
+ qCDebug(lcProjectResolver) << "scanner condition is false";
return;
}
@@ -965,24 +967,12 @@ void ProjectResolver::resolveScanner(Item *item, ProjectResolver::ProjectContext
m_productContext->product->scanners += scanner;
}
-static ModuleLoaderResult::ProductInfo::Dependency extractDependency(
- const ResolvedProductConstPtr &product)
-{
- ModuleLoaderResult::ProductInfo::Dependency dependency;
- dependency.name = product->name;
- dependency.profile = product->profile;
- dependency.multiplexConfigurationId = product->multiplexConfigurationId;
- return dependency;
-}
-
ProjectResolver::ProductDependencyInfos ProjectResolver::getProductDependencies(
const ResolvedProductConstPtr &product, const ModuleLoaderResult::ProductInfo &productInfo)
{
ProductDependencyInfos result;
result.dependencies.reserve(productInfo.usedProducts.size());
- QList<ModuleLoaderResult::ProductInfo::Dependency> dependencies = productInfo.usedProducts;
- for (int i = dependencies.count() - 1; i >= 0; --i) {
- const ModuleLoaderResult::ProductInfo::Dependency &dependency = dependencies.at(i);
+ for (const auto &dependency : productInfo.usedProducts) {
QBS_CHECK(dependency.name.isEmpty() != dependency.productTypes.isEmpty());
if (!dependency.productTypes.isEmpty()) {
for (const FileTag &tag : dependency.productTypes) {
@@ -993,10 +983,8 @@ ProjectResolver::ProductDependencyInfos ProjectResolver::getProductDependencies(
continue;
}
result.dependencies.emplace_back(p, dependency.parameters);
- dependencies << extractDependency(p);
}
}
- dependencies.removeAt(i);
} else if (dependency.profile == QLatin1String("*")) {
for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) {
if (p->name != dependency.name || p == product || !p->enabled
@@ -1004,28 +992,31 @@ ProjectResolver::ProductDependencyInfos ProjectResolver::getProductDependencies(
continue;
}
result.dependencies.emplace_back(p, dependency.parameters);
- dependencies << extractDependency(p);
}
- dependencies.removeAt(i);
} else {
- ResolvedProductPtr usedProduct;
- if (!dependency.multiplexConfigurationId.isEmpty()) {
- usedProduct = m_productsByName.value(dependency.uniqueName());
- } else {
+ ResolvedProductPtr usedProduct = m_productsByName.value(dependency.uniqueName());
+ const QString depDisplayName = ResolvedProduct::fullDisplayName(dependency.name,
+ dependency.multiplexConfigurationId);
+ if (!usedProduct) {
+ throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
+ .arg(product->fullDisplayName(), depDisplayName),
+ product->location);
+ }
+ if (!dependency.profile.isEmpty() && usedProduct->profile != dependency.profile) {
+ usedProduct.reset();
for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) {
if (p->name == dependency.name && p->profile == dependency.profile) {
usedProduct = p;
break;
}
}
- }
- if (!usedProduct) {
- const ResolvedProductConstPtr p = m_productsByName.value(dependency.uniqueName());
- ErrorInfo e(Tr::tr("Product '%1' depends on '%2', which does not exist.")
- .arg(product->name, dependency.uniqueName()), product->location);
- if (p)
- e.append(Tr::tr("Requested profile was '%1'.").arg(dependency.profile));
- throw e;
+ if (!usedProduct) {
+ throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
+ "for the requested profile '%3'.")
+ .arg(product->fullDisplayName(), depDisplayName,
+ dependency.profile),
+ product->location);
+ }
}
if (!usedProduct->enabled) {
if (!dependency.isRequired)
@@ -1182,18 +1173,25 @@ void ProjectResolver::postProcess(const ResolvedProductPtr &product,
ProjectContext *projectContext) const
{
product->fileTaggers += projectContext->fileTaggers;
- for (const RulePtr &rule : qAsConst(projectContext->rules))
- product->rules += rule;
+ std::sort(std::begin(product->fileTaggers), std::end(product->fileTaggers),
+ [] (const FileTaggerConstPtr &a, const FileTaggerConstPtr &b) {
+ return a->priority() > b->priority();
+ });
+ for (const RulePtr &rule : qAsConst(projectContext->rules)) {
+ RulePtr clonedRule = rule->clone();
+ clonedRule->product = product.get();
+ product->rules += clonedRule;
+ }
}
void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const
{
for (const SourceArtifactPtr &artifact : product->allEnabledFiles())
- applyFileTaggers(artifact, product, m_logger);
+ applyFileTaggers(artifact, product);
}
void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact,
- const ResolvedProductConstPtr &product, Logger &logger)
+ const ResolvedProductConstPtr &product)
{
if (!artifact->overrideFileTags || artifact->fileTags.isEmpty()) {
const QString fileName = FileInfo::fileName(artifact->absoluteFilePath);
@@ -1201,9 +1199,8 @@ void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact,
artifact->fileTags.unite(fileTags);
if (artifact->fileTags.isEmpty())
artifact->fileTags.insert(unknownFileTag());
- if (logger.traceEnabled())
- logger.qbsTrace() << "[PR] adding file tags " << artifact->fileTags
- << " to " << fileName;
+ qCDebug(lcProjectResolver) << "adding file tags" << artifact->fileTags
+ << "to" << fileName;
}
}
@@ -1263,14 +1260,12 @@ QVariantMap ProjectResolver::evaluateProperties(const Item *item, const Item *pr
// NOTE: Loses type information if scriptValue.isUndefined == true,
// as such QScriptValues become invalid QVariants.
QVariant v = scriptValue.toVariant();
- if (pd.type() == PropertyDeclaration::Path && v.isValid())
- v = convertPathProperty(v.toString(),
- m_productContext->product->sourceDirectory);
- else if (pd.type() == PropertyDeclaration::PathList)
- v = convertPathListProperty(v.toStringList(),
- m_productContext->product->sourceDirectory);
- else if (pd.type() == PropertyDeclaration::StringList)
+ if (pd.type() == PropertyDeclaration::Path && v.isValid()) {
+ v = v.toString();
+ } else if (pd.type() == PropertyDeclaration::PathList
+ || pd.type() == PropertyDeclaration::StringList) {
v = v.toStringList();
+ }
result[it.key()] = v;
break;
}
@@ -1297,25 +1292,13 @@ QVariantMap ProjectResolver::evaluateProperties(const Item *item, const Item *pr
QVariantMap ProjectResolver::createProductConfig()
{
EvalCacheEnabler cachingEnabler(m_evaluator);
+ m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory);
QVariantMap cfg = evaluateModuleValues(m_productContext->item);
cfg = evaluateProperties(m_productContext->item, m_productContext->item, cfg);
+ m_evaluator->clearPathPropertiesBaseDir();
return cfg;
}
-QString ProjectResolver::convertPathProperty(const QString &path, const QString &dirPath) const
-{
- return path.isEmpty() ? path : QDir::cleanPath(FileInfo::resolvePath(dirPath, path));
-}
-
-QStringList ProjectResolver::convertPathListProperty(const QStringList &paths,
- const QString &dirPath) const
-{
- QStringList result;
- for (const QString &path : paths)
- result += convertPathProperty(path, dirPath);
- return result;
-}
-
void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item,
ProjectContext *projectContext)
{
diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h
index f25db0651..2bb05616a 100644
--- a/src/lib/corelib/language/projectresolver.h
+++ b/src/lib/corelib/language/projectresolver.h
@@ -73,7 +73,7 @@ public:
TopLevelProjectPtr resolve();
static void applyFileTaggers(const SourceArtifactPtr &artifact,
- const ResolvedProductConstPtr &product, Logger &logger);
+ const ResolvedProductConstPtr &product);
static SourceArtifactPtr createSourceArtifact(const ResolvedProductPtr &rproduct,
const QString &fileName, const GroupPtr &group, bool wildcard,
@@ -98,6 +98,7 @@ private:
void resolveModules(const Item *item, ProjectContext *projectContext);
void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
const QVariantMap &parameters, ProjectContext *projectContext);
+ void gatherProductTypes(ResolvedProduct *product, Item *item);
QVariantMap resolveAdditionalModuleProperties(const Item *group,
const QVariantMap &currentValues);
void resolveGroup(Item *item, ProjectContext *projectContext);
@@ -116,8 +117,6 @@ private:
QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer,
const QVariantMap &tmplt, bool lookupPrototype = true);
QVariantMap createProductConfig();
- QString convertPathProperty(const QString &path, const QString &dirPath) const;
- QStringList convertPathListProperty(const QStringList &paths, const QString &dirPath) const;
ProjectContext createProjectContext(ProjectContext *parentProjectContext) const;
struct ProductDependencyInfo
diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp
index a248f0d0a..5a48f7720 100644
--- a/src/lib/corelib/language/scriptengine.cpp
+++ b/src/lib/corelib/language/scriptengine.cpp
@@ -64,6 +64,7 @@
#include <QtScript/qscriptclass.h>
#include <QtScript/qscriptvalueiterator.h>
+#include <functional>
#include <set>
namespace qbs {
@@ -120,6 +121,20 @@ ScriptEngine::~ScriptEngine()
delete m_modulePropertyScriptClass;
}
+QScriptValue ScriptEngine::evaluate(const QString &program, const QString &fileName, int lineNumber)
+{
+ QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
+ releaseResourcesOfScriptObjects();
+ return result;
+}
+
+QScriptValue ScriptEngine::evaluate(const QScriptProgram &program)
+{
+ QScriptValue result = QScriptEngine::evaluate(program);
+ releaseResourcesOfScriptObjects();
+ return result;
+}
+
void ScriptEngine::import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject)
{
installImportFunctions();
@@ -466,9 +481,9 @@ void ScriptEngine::setModulePropertyScriptClass(QScriptClass *modulePropertyScri
m_modulePropertyScriptClass = modulePropertyScriptClass;
}
-void ScriptEngine::addEnvironmentVariable(const QString &name, const QString &value)
+void ScriptEngine::addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj)
{
- m_usedEnvironment.insert(name, value);
+ m_resourceAcquiringScriptObjects.push_back(obj);
}
void ScriptEngine::addCanonicalFilePathResult(const QString &filePath,
@@ -548,6 +563,13 @@ void ScriptEngine::abort()
abortEvaluation(m_cancelationError);
}
+void ScriptEngine::releaseResourcesOfScriptObjects()
+{
+ std::for_each(m_resourceAcquiringScriptObjects.begin(), m_resourceAcquiringScriptObjects.end(),
+ std::mem_fn(&ResourceAcquiringScriptObject::releaseResources));
+ m_resourceAcquiringScriptObjects.clear();
+}
+
class JSTypeExtender
{
public:
diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h
index da0b6028c..d9ddffdb0 100644
--- a/src/lib/corelib/language/scriptengine.h
+++ b/src/lib/corelib/language/scriptengine.h
@@ -76,6 +76,18 @@ public:
};
using DubiousContextList = std::vector<DubiousContext>;
+
+/*
+ * ScriptObject that acquires resources, for example a file handle.
+ * The ScriptObject should have QtOwnership and deleteLater() itself in releaseResources.
+ */
+class ResourceAcquiringScriptObject
+{
+public:
+ virtual void releaseResources() = 0;
+};
+
+
class QBS_AUTOTEST_EXPORT ScriptEngine : public QScriptEngine
{
Q_OBJECT
@@ -83,6 +95,10 @@ public:
ScriptEngine(Logger &logger, EvalContext evalContext, QObject *parent = 0);
~ScriptEngine();
+ QScriptValue evaluate(const QString &program, const QString &fileName = QString(),
+ int lineNumber = 1);
+ QScriptValue evaluate(const QScriptProgram &program);
+
Logger &logger() const { return m_logger; }
void import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject);
void import(const JsImport &jsImport, QScriptValue &targetObject);
@@ -122,8 +138,6 @@ public:
QProcessEnvironment environment() const;
void setEnvironment(const QProcessEnvironment &env);
- void addEnvironmentVariable(const QString &name, const QString &value);
- QHash<QString, QString> usedEnvironment() const { return m_usedEnvironment; }
void addCanonicalFilePathResult(const QString &filePath, const QString &resultFilePath);
void addFileExistsResult(const QString &filePath, bool exists);
void addDirectoryEntriesResult(const QString &path, QDir::Filters filters,
@@ -184,10 +198,13 @@ public:
QScriptClass *modulePropertyScriptClass() const;
void setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass);
+ void addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj);
+
private:
QScriptValue newFunction(FunctionWithArgSignature signature, void *arg) Q_DECL_EQ_DELETE;
void abort();
+ void releaseResourcesOfScriptObjects();
void installQbsBuiltins();
void extendJavaScriptBuiltins();
@@ -232,7 +249,6 @@ private:
QScriptValue m_definePropertyFunction;
QScriptValue m_emptyFunction;
QProcessEnvironment m_environment;
- QHash<QString, QString> m_usedEnvironment;
QHash<QString, QString> m_canonicalFilePathResult;
QHash<QString, bool> m_fileExistsResult;
QHash<std::pair<QString, quint32>, QStringList> m_directoryEntriesResult;
@@ -248,6 +264,7 @@ private:
QList<QVariantMap *> m_ownedVariantMaps;
qint64 m_elapsedTimeImporting = -1;
EvalContext m_evalContext;
+ std::vector<ResourceAcquiringScriptObject *> m_resourceAcquiringScriptObjects;
};
class EvalContextSwitcher
diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h
index 6b0c98dc9..e175651cc 100644
--- a/src/lib/corelib/language/value.h
+++ b/src/lib/corelib/language/value.h
@@ -75,12 +75,13 @@ public:
void setNext(const ValuePtr &next);
bool createdByPropertiesBlock() const { return m_createdByPropertiesBlock; }
+ void setCreatedByPropertiesBlock(bool b) { m_createdByPropertiesBlock = b; }
private:
Type m_type;
Item *m_definingItem;
ValuePtr m_next;
- const bool m_createdByPropertiesBlock;
+ bool m_createdByPropertiesBlock;
};
class ValueHandler
diff --git a/src/lib/corelib/logging/categories.cpp b/src/lib/corelib/logging/categories.cpp
new file mode 100644
index 000000000..0f844f5b4
--- /dev/null
+++ b/src/lib/corelib/logging/categories.cpp
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "categories.h"
+
+namespace qbs {
+namespace Internal {
+
+Q_LOGGING_CATEGORY(lcBuildGraph, "qbs.buildgraph", QtCriticalMsg)
+Q_LOGGING_CATEGORY(lcDepScan, "qbs.depscan", QtCriticalMsg)
+Q_LOGGING_CATEGORY(lcExec, "qbs.exec", QtCriticalMsg)
+Q_LOGGING_CATEGORY(lcMocScan, "qbs.mocscan", QtCriticalMsg)
+Q_LOGGING_CATEGORY(lcModuleLoader, "qbs.moduleloader", QtCriticalMsg)
+Q_LOGGING_CATEGORY(lcPluginManager, "qbs.pluginmanager", QtCriticalMsg)
+Q_LOGGING_CATEGORY(lcProjectResolver, "qbs.projectresolver", QtCriticalMsg)
+Q_LOGGING_CATEGORY(lcUpToDateCheck, "qbs.uptodate", QtCriticalMsg)
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/logging/categories.h b/src/lib/corelib/logging/categories.h
new file mode 100644
index 000000000..40c69845e
--- /dev/null
+++ b/src/lib/corelib/logging/categories.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef CATEGORIES_H
+#define CATEGORIES_H
+
+#include <QtCore/qloggingcategory.h>
+
+namespace qbs {
+namespace Internal {
+
+Q_DECLARE_LOGGING_CATEGORY(lcBuildGraph)
+Q_DECLARE_LOGGING_CATEGORY(lcDepScan)
+Q_DECLARE_LOGGING_CATEGORY(lcExec)
+Q_DECLARE_LOGGING_CATEGORY(lcMocScan)
+Q_DECLARE_LOGGING_CATEGORY(lcModuleLoader)
+Q_DECLARE_LOGGING_CATEGORY(lcPluginManager)
+Q_DECLARE_LOGGING_CATEGORY(lcProjectResolver)
+Q_DECLARE_LOGGING_CATEGORY(lcUpToDateCheck)
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // CATEGORIES_H
diff --git a/src/lib/corelib/logging/logger.h b/src/lib/corelib/logging/logger.h
index aee59dde4..b0b5dd6f3 100644
--- a/src/lib/corelib/logging/logger.h
+++ b/src/lib/corelib/logging/logger.h
@@ -45,6 +45,7 @@
#include <tools/error.h>
#include <QtCore/qbytearray.h>
+#include <QtCore/qloggingcategory.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
diff --git a/src/lib/corelib/logging/logging.pri b/src/lib/corelib/logging/logging.pri
index 029a4fb30..3a4379a9a 100644
--- a/src/lib/corelib/logging/logging.pri
+++ b/src/lib/corelib/logging/logging.pri
@@ -1,11 +1,13 @@
include(../../../install_prefix.pri)
HEADERS += \
+ $$PWD/categories.h \
$$PWD/logger.h \
$$PWD/translator.h \
$$PWD/ilogsink.h
SOURCES += \
+ $$PWD/categories.cpp \
$$PWD/logger.cpp \
$$PWD/ilogsink.cpp
diff --git a/src/lib/corelib/tools/executablefinder.cpp b/src/lib/corelib/tools/executablefinder.cpp
index ce259ee63..816c0e7d0 100644
--- a/src/lib/corelib/tools/executablefinder.cpp
+++ b/src/lib/corelib/tools/executablefinder.cpp
@@ -42,6 +42,8 @@
#include "fileinfo.h"
#include "hostosinfo.h"
+#include <logging/categories.h>
+
#include <QtCore/qdir.h>
namespace qbs {
@@ -61,10 +63,9 @@ static QStringList populateExecutableSuffixes()
QStringList ExecutableFinder::m_executableSuffixes = populateExecutableSuffixes();
ExecutableFinder::ExecutableFinder(const ResolvedProductPtr &m_product,
- const QProcessEnvironment &env, const Logger &logger)
+ const QProcessEnvironment &env)
: m_product(m_product)
, m_environment(env)
- , m_logger(logger)
{
}
@@ -86,8 +87,7 @@ QString ExecutableFinder::findBySuffix(const QString &filePath) const
return fullProgramPath;
fullProgramPath = filePath;
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[EXEC] looking for executable by suffix " << fullProgramPath;
+ qCDebug(lcExec) << "looking for executable by suffix" << fullProgramPath;
const QString emptyDirectory;
candidateCheck(emptyDirectory, fullProgramPath, fullProgramPath);
cacheFilePath(filePath, fullProgramPath);
@@ -100,8 +100,7 @@ bool ExecutableFinder::candidateCheck(const QString &directory, const QString &p
{
for (int i = 0; i < m_executableSuffixes.count(); ++i) {
QString candidate = directory + program + m_executableSuffixes.at(i);
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[EXEC] candidate: " << candidate;
+ qCDebug(lcExec) << "candidate:" << candidate;
QFileInfo fi(candidate);
if (fi.isFile() && fi.isExecutable()) {
fullProgramPath = candidate;
@@ -118,8 +117,7 @@ QString ExecutableFinder::findInPath(const QString &filePath, const QString &wor
return fullProgramPath;
fullProgramPath = filePath;
- if (m_logger.traceEnabled())
- m_logger.qbsTrace() << "[EXEC] looking for executable in PATH " << fullProgramPath;
+ qCDebug(lcExec) << "looking for executable in PATH" << fullProgramPath;
QStringList pathEnv = m_environment.value(QLatin1String("PATH"))
.split(HostOsInfo::pathListSeparator(), QString::SkipEmptyParts);
if (HostOsInfo::isWindowsHost())
diff --git a/src/lib/corelib/tools/executablefinder.h b/src/lib/corelib/tools/executablefinder.h
index 949013f5a..cb965b5d1 100644
--- a/src/lib/corelib/tools/executablefinder.h
+++ b/src/lib/corelib/tools/executablefinder.h
@@ -41,7 +41,6 @@
#define QBS_EXECUTABLEFINDER_H
#include <language/language.h>
-#include <logging/logger.h>
#include <QtCore/qprocess.h>
@@ -54,8 +53,7 @@ namespace Internal {
class ExecutableFinder
{
public:
- ExecutableFinder(const ResolvedProductPtr &product, const QProcessEnvironment &env,
- const Logger &logger);
+ ExecutableFinder(const ResolvedProductPtr &product, const QProcessEnvironment &env);
QString findExecutable(const QString &path, const QString &workingDirPath);
@@ -71,7 +69,6 @@ private:
ResolvedProductPtr m_product;
const QProcessEnvironment &m_environment;
- Logger m_logger;
};
} // namespace Internal
diff --git a/src/lib/corelib/tools/filesaver.cpp b/src/lib/corelib/tools/filesaver.cpp
index eb41df7de..db795b012 100644
--- a/src/lib/corelib/tools/filesaver.cpp
+++ b/src/lib/corelib/tools/filesaver.cpp
@@ -38,36 +38,42 @@
****************************************************************************/
#include "filesaver.h"
+#include "stlutils.h"
#include <QtCore/qfile.h>
#include <QtCore/qsavefile.h>
+#include <tools/iosutils.h>
+
+#include <fstream>
+
namespace qbs {
namespace Internal {
-FileSaver::FileSaver(const QString &filePath, bool overwriteIfUnchanged)
+FileSaver::FileSaver(const std::string &filePath, bool overwriteIfUnchanged)
: m_filePath(filePath), m_overwriteIfUnchanged(overwriteIfUnchanged)
{
}
-QIODevice *FileSaver::device()
+std::ostream *FileSaver::device()
{
- return m_memoryDevice.data();
+ return m_memoryDevice.get();
}
bool FileSaver::open()
{
if (!m_overwriteIfUnchanged) {
- QFile file(m_filePath);
- if (file.open(QIODevice::ReadOnly))
- m_oldFileContents = file.readAll();
+ std::ifstream file(utf8_to_native_path(m_filePath));
+ if (file.is_open())
+ m_oldFileContents.assign(std::istreambuf_iterator<std::ifstream::char_type>(file),
+ std::istreambuf_iterator<std::ifstream::char_type>());
else
m_oldFileContents.clear();
}
m_newFileContents.clear();
- m_memoryDevice.reset(new QBuffer(&m_newFileContents));
- return m_memoryDevice->open(QIODevice::WriteOnly);
+ m_memoryDevice = std::make_shared<std::ostringstream>(m_newFileContents);
+ return true;
}
bool FileSaver::commit()
@@ -75,17 +81,26 @@ bool FileSaver::commit()
if (!m_overwriteIfUnchanged && m_oldFileContents == m_newFileContents)
return true; // no need to write unchanged data
- QSaveFile saveFile(m_filePath);
- if (!saveFile.open(QIODevice::WriteOnly))
+ const std::string tempFilePath = std::tmpnam(nullptr);
+ std::ofstream tempFile(utf8_to_native_path(tempFilePath));
+ if (!tempFile.is_open())
+ return false;
+
+ tempFile.write(m_newFileContents.data(), m_newFileContents.size());
+ if (!tempFile.good())
return false;
- saveFile.write(m_newFileContents);
- return saveFile.commit();
+ return Internal::rename(tempFilePath, m_filePath) == 0;
+}
+
+size_t FileSaver::write(const std::vector<char> &data)
+{
+ return fwrite(data, device());
}
-qint64 FileSaver::write(const QByteArray &data)
+size_t FileSaver::write(const std::string &data)
{
- return device()->write(data);
+ return fwrite(data, device());
}
} // namespace Internal
diff --git a/src/lib/corelib/tools/filesaver.h b/src/lib/corelib/tools/filesaver.h
index 07ba5ac25..bee04405f 100644
--- a/src/lib/corelib/tools/filesaver.h
+++ b/src/lib/corelib/tools/filesaver.h
@@ -42,10 +42,10 @@
#include "qbs_export.h"
-#include <QtCore/qbuffer.h>
-#include <QtCore/qbytearray.h>
-#include <QtCore/qscopedpointer.h>
-#include <QtCore/qstring.h>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
namespace qbs {
namespace Internal {
@@ -55,18 +55,19 @@ namespace Internal {
*/
class QBS_EXPORT FileSaver {
public:
- FileSaver(const QString &filePath, bool overwriteIfUnchanged = false);
+ FileSaver(const std::string &filePath, bool overwriteIfUnchanged = false);
- QIODevice *device();
+ std::ostream *device();
bool open();
bool commit();
- qint64 write(const QByteArray &data);
+ size_t write(const std::vector<char> &data);
+ size_t write(const std::string &data);
private:
- QByteArray m_newFileContents;
- QByteArray m_oldFileContents;
- QScopedPointer<QBuffer> m_memoryDevice;
- const QString m_filePath;
+ std::string m_newFileContents;
+ std::string m_oldFileContents;
+ std::shared_ptr<std::ostringstream> m_memoryDevice;
+ const std::string m_filePath;
const bool m_overwriteIfUnchanged;
};
diff --git a/src/lib/corelib/tools/hostosinfo.h b/src/lib/corelib/tools/hostosinfo.h
index 6d9aca96f..d5a53d877 100644
--- a/src/lib/corelib/tools/hostosinfo.h
+++ b/src/lib/corelib/tools/hostosinfo.h
@@ -72,6 +72,7 @@ public:
// Add more as needed.
enum HostOs { HostOsWindows, HostOsLinux, HostOsMacos, HostOsOtherUnix, HostOsOther };
+ static inline std::vector<std::string> hostOSIdentifiers();
static inline HostOs hostOs();
static inline Version hostOsVersion() {
@@ -143,6 +144,36 @@ public:
}
};
+std::vector<std::string> HostOsInfo::hostOSIdentifiers()
+{
+#if defined(__APPLE__)
+ return {"macos", "darwin", "bsd", "unix"};
+#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
+ return {"windows"};
+#elif defined(_AIX)
+ return {"aix", "unix"};
+#elif defined(hpux) || defined(__hpux)
+ return {"hpux", "unix"};
+#elif defined(__sun) || defined(sun)
+ return {"solaris", "unix"};
+#elif defined(__linux__) || defined(__linux)
+ return {"linux", "unix"};
+#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+ return {"freebsd", "bsd", "unix"};
+#elif defined(__NetBSD__)
+ return {"netbsd", "bsd", "unix"};
+#elif defined(__OpenBSD__)
+ return {"openbsd", "bsd", "unix"};
+#elif defined(__GNU__)
+ return {"hurd", "unix"};
+#elif defined(__HAIKU__)
+ return {"haiku"};
+#else
+ #warning "Qbs has not been ported to this OS - see http://qbs.io/"
+ return {};
+#endif
+}
+
HostOsInfo::HostOs HostOsInfo::hostOs()
{
#if defined(Q_OS_WIN)
diff --git a/src/lib/corelib/tools/iosutils.h b/src/lib/corelib/tools/iosutils.h
new file mode 100644
index 000000000..7934be512
--- /dev/null
+++ b/src/lib/corelib/tools/iosutils.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBS_IOSUTILS_H
+#define QBS_IOSUTILS_H
+
+#include <cstdio>
+#include <ostream>
+
+#if defined(_WIN32)
+#include <codecvt>
+#define QBS_RENAME_IMPL ::_wrename
+typedef std::wstring qbs_filesystem_path_string_type;
+#else
+#define QBS_RENAME_IMPL ::rename
+typedef std::string qbs_filesystem_path_string_type;
+#endif
+
+namespace qbs {
+namespace Internal {
+
+static inline bool fwrite(const char *values, size_t nitems, std::ostream *stream)
+{
+ stream->write(values, nitems);
+ return stream->good();
+}
+
+template <class C>
+bool fwrite(const C &container, std::ostream *stream)
+{
+ return fwrite(&*(std::begin(container)), container.size(), stream);
+}
+
+static inline bool fwrite(const char *s, std::ostream *stream)
+{
+ return fwrite(s, strlen(s), stream);
+}
+
+static inline qbs_filesystem_path_string_type utf8_to_native_path(const std::string &str)
+{
+#if defined(_WIN32)
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
+ return converter.from_bytes(str);
+#else
+ return str;
+#endif
+}
+
+static int rename(const std::string &oldName, const std::string &newName)
+{
+ const auto wOldName = utf8_to_native_path(oldName);
+ const auto wNewName = utf8_to_native_path(newName);
+ return QBS_RENAME_IMPL(wOldName.c_str(), wNewName.c_str());
+}
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // QBS_IOSUTILS_H
diff --git a/src/lib/corelib/tools/msvcinfo.cpp b/src/lib/corelib/tools/msvcinfo.cpp
index b769e3910..4d44b6433 100644
--- a/src/lib/corelib/tools/msvcinfo.cpp
+++ b/src/lib/corelib/tools/msvcinfo.cpp
@@ -138,16 +138,28 @@ static QStringList parseCommandLine(const QString &commandLine)
#endif
static QVariantMap getMsvcDefines(const QString &compilerFilePath,
- const QProcessEnvironment &compilerEnv)
+ const QProcessEnvironment &compilerEnv,
+ MSVC::CompilerLanguage language)
{
#ifdef Q_OS_WIN
+ QString backendSwitch, languageSwitch;
+ switch (language) {
+ case MSVC::CLanguage:
+ backendSwitch = QStringLiteral("/B1");
+ languageSwitch = QStringLiteral("/TC");
+ break;
+ case MSVC::CPlusPlusLanguage:
+ backendSwitch = QStringLiteral("/Bx");
+ languageSwitch = QStringLiteral("/TP");
+ break;
+ }
const QByteArray commands("set MSC_CMD_FLAGS\n");
QStringList out = QString::fromLocal8Bit(runProcess(compilerFilePath, QStringList()
<< QStringLiteral("/nologo")
- << QStringLiteral("/B1")
+ << backendSwitch
<< QString::fromWCharArray(_wgetenv(L"COMSPEC"))
<< QStringLiteral("/c")
- << QStringLiteral("/TC")
+ << languageSwitch
<< QStringLiteral("NUL"),
compilerEnv, true, commands)).split(QLatin1Char('\n'));
@@ -175,6 +187,7 @@ static QVariantMap getMsvcDefines(const QString &compilerFilePath,
#else
Q_UNUSED(compilerFilePath);
Q_UNUSED(compilerEnv);
+ Q_UNUSED(language);
return QVariantMap();
#endif
}
@@ -198,9 +211,10 @@ QString MSVC::clPathForArchitecture(const QString &arch) const
return binPathForArchitecture(arch) + QLatin1String("/cl.exe");
}
-QVariantMap MSVC::compilerDefines(const QString &compilerFilePath) const
+QVariantMap MSVC::compilerDefines(const QString &compilerFilePath,
+ MSVC::CompilerLanguage language) const
{
- return getMsvcDefines(compilerFilePath, environment);
+ return getMsvcDefines(compilerFilePath, environment, language);
}
void MSVC::determineCompilerVersion()
diff --git a/src/lib/corelib/tools/msvcinfo.h b/src/lib/corelib/tools/msvcinfo.h
index 733d8a90f..664557777 100644
--- a/src/lib/corelib/tools/msvcinfo.h
+++ b/src/lib/corelib/tools/msvcinfo.h
@@ -60,6 +60,11 @@ namespace Internal {
class MSVC
{
public:
+ enum CompilerLanguage {
+ CLanguage = 0,
+ CPlusPlusLanguage = 1
+ };
+
QString version;
Version internalVsVersion;
Version compilerVersion;
@@ -88,7 +93,8 @@ public:
QBS_EXPORT void init();
QBS_EXPORT QString binPathForArchitecture(const QString &arch) const;
QBS_EXPORT QString clPathForArchitecture(const QString &arch) const;
- QBS_EXPORT QVariantMap compilerDefines(const QString &compilerFilePath) const;
+ QBS_EXPORT QVariantMap compilerDefines(const QString &compilerFilePath,
+ CompilerLanguage language) const;
private:
void determineCompilerVersion();
diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp
index bf8da20e8..1d236fb6b 100644
--- a/src/lib/corelib/tools/persistence.cpp
+++ b/src/lib/corelib/tools/persistence.cpp
@@ -50,7 +50,7 @@
namespace qbs {
namespace Internal {
-static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-104";
+static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE_107";
NoBuildGraphError::NoBuildGraphError()
: ErrorInfo(Tr::tr("No build graph exists yet for this configuration."))
diff --git a/src/lib/corelib/tools/persistence.h b/src/lib/corelib/tools/persistence.h
index f0d45c83e..9d425c83d 100644
--- a/src/lib/corelib/tools/persistence.h
+++ b/src/lib/corelib/tools/persistence.h
@@ -220,6 +220,20 @@ struct PersistentPool::Helper<std::shared_ptr<T>,
};
template<typename T>
+struct PersistentPool::Helper<std::unique_ptr<T>,
+ typename std::enable_if<IsPersistentObject<T>::value>::type>
+{
+ static void store(const std::unique_ptr<T> &value, PersistentPool *pool)
+ {
+ pool->store(value.get());
+ }
+ static void load(std::unique_ptr<T> &ptr, PersistentPool *pool)
+ {
+ ptr.reset(pool->idLoad<typename std::remove_const<T>::type>());
+ }
+};
+
+template<typename T>
struct PersistentPool::Helper<T *, typename std::enable_if<IsPersistentObject<T>::value>::type>
{
static void store(const T *value, PersistentPool *pool) { pool->storePersistentObject(value); }
diff --git a/src/lib/corelib/tools/profile.cpp b/src/lib/corelib/tools/profile.cpp
index 37c1dc64f..f4b0ff5b2 100644
--- a/src/lib/corelib/tools/profile.cpp
+++ b/src/lib/corelib/tools/profile.cpp
@@ -62,14 +62,19 @@ namespace qbs {
/*!
* \brief Creates an object giving access to the settings for profile \c name.
*/
-Profile::Profile(const QString &name, Settings *settings) : m_name(name), m_settings(settings)
+Profile::Profile(const QString &name, Settings *settings, const QVariantMap &profiles)
+ : m_name(name),
+ m_settings(settings),
+ m_values(profiles.value(name).toMap()),
+ m_profiles(profiles)
{
QBS_ASSERT(name == cleanName(name), return);
}
bool Profile::exists() const
{
- return !m_settings->allKeysWithPrefix(profileKey()).isEmpty();
+ return m_name == fallbackName() || !m_values.isEmpty()
+ || !m_settings->allKeysWithPrefix(profileKey()).isEmpty();
}
/*!
@@ -192,7 +197,10 @@ void Profile::checkBaseProfileExistence(const Profile &baseProfile) const
QVariant Profile::localValue(const QString &key) const
{
- return m_settings->value(fullyQualifiedKey(key));
+ QVariant val = m_values.value(key);
+ if (!val.isValid())
+ val = m_settings->value(fullyQualifiedKey(key));
+ return val;
}
QString Profile::fullyQualifiedKey(const QString &key) const
@@ -210,7 +218,7 @@ QVariant Profile::possiblyInheritedValue(const QString &key, const QVariant &def
const QString baseProfileName = baseProfile();
if (baseProfileName.isEmpty())
return defaultValue;
- Profile parentProfile(baseProfileName, m_settings);
+ Profile parentProfile(baseProfileName, m_settings, m_profiles);
checkBaseProfileExistence(parentProfile);
return parentProfile.possiblyInheritedValue(key, defaultValue, profileChain);
}
@@ -219,13 +227,15 @@ QStringList Profile::allKeysInternal(Profile::KeySelection selection,
QStringList profileChain) const
{
extendAndCheckProfileChain(profileChain);
- QStringList keys = m_settings->allKeysWithPrefix(profileKey());
+ QStringList keys = m_values.keys();
+ if (keys.isEmpty())
+ keys = m_settings->allKeysWithPrefix(profileKey());
if (selection == KeySelectionNonRecursive)
return keys;
const QString baseProfileName = baseProfile();
if (baseProfileName.isEmpty())
return keys;
- Profile parentProfile(baseProfileName, m_settings);
+ Profile parentProfile(baseProfileName, m_settings, m_profiles);
checkBaseProfileExistence(parentProfile);
keys += parentProfile.allKeysInternal(KeySelectionRecursive, profileChain);
keys.removeDuplicates();
diff --git a/src/lib/corelib/tools/profile.h b/src/lib/corelib/tools/profile.h
index 3f207e09e..4dd78c0ca 100644
--- a/src/lib/corelib/tools/profile.h
+++ b/src/lib/corelib/tools/profile.h
@@ -52,7 +52,7 @@ class Settings;
class QBS_EXPORT Profile
{
public:
- explicit Profile(const QString &name, Settings *settings);
+ Profile(const QString &name, Settings *settings, const QVariantMap &profiles = QVariantMap());
bool exists() const;
QVariant value(const QString &key, const QVariant &defaultValue = QVariant(),
@@ -88,6 +88,8 @@ private:
QString m_name;
Settings *m_settings;
+ QVariantMap m_values;
+ QVariantMap m_profiles;
};
namespace Internal {
diff --git a/src/lib/corelib/tools/qbspluginmanager.cpp b/src/lib/corelib/tools/qbspluginmanager.cpp
index 9635f2bac..a83e1e368 100644
--- a/src/lib/corelib/tools/qbspluginmanager.cpp
+++ b/src/lib/corelib/tools/qbspluginmanager.cpp
@@ -40,6 +40,7 @@
#include "qbspluginmanager.h"
#include <logging/logger.h>
+#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/hostosinfo.h>
#include <tools/qbsassert.h>
@@ -115,8 +116,7 @@ void QbsPluginManager::loadPlugins(const QStringList &pluginPaths, const Logger
filters << QLatin1String("*.so");
for (const QString &pluginPath : pluginPaths) {
- logger.qbsTrace() << QString::fromLatin1("plugin manager: loading plugins from '%1'.")
- .arg(QDir::toNativeSeparators(pluginPath));
+ qCDebug(lcPluginManager) << "loading plugins from" << QDir::toNativeSeparators(pluginPath);
QDirIterator it(pluginPath, filters, QDir::Files);
while (it.hasNext()) {
const QString fileName = it.next();
@@ -131,8 +131,8 @@ void QbsPluginManager::loadPlugins(const QStringList &pluginPaths, const Logger
lib->resolve("QbsPluginLoad"));
if (load) {
load();
- logger.qbsTrace() << QString::fromLatin1("pluginmanager: plugin '%1' loaded.")
- .arg(QDir::toNativeSeparators(fileName));
+ qCDebug(lcPluginManager) << "plugin" << QDir::toNativeSeparators(fileName)
+ << "loaded.";
m_libs.push_back(lib.take());
} else {
logger.qbsWarning() << Tr::tr("plugin manager: not a qbs plugin");
diff --git a/src/lib/corelib/tools/set.h b/src/lib/corelib/tools/set.h
index 5a24a1685..a7f4f37b1 100644
--- a/src/lib/corelib/tools/set.h
+++ b/src/lib/corelib/tools/set.h
@@ -274,11 +274,8 @@ template<typename T> void Set<T>::load(PersistentPool &pool)
template<typename T> void Set<T>::store(PersistentPool &pool) const
{
pool.store(count());
-
- // Does not work; reason unknown.
-// std::for_each(m_data.cbegin(), m_data.cend(), std::bind(&Set<T>::storeElem, this,
-// std::ref(pool), std::placeholders::_2));
- std::for_each(m_data.cbegin(), m_data.cend(), [this, &pool](const T &e) { storeElem(pool, e); });
+ std::for_each(m_data.cbegin(), m_data.cend(),
+ std::bind(&Set<T>::storeElem, this, std::ref(pool), std::placeholders::_1));
}
template<typename T> QStringList Set<T>::toStringList() const
diff --git a/src/lib/corelib/tools/settings.cpp b/src/lib/corelib/tools/settings.cpp
index f4649df0b..054f7b720 100644
--- a/src/lib/corelib/tools/settings.cpp
+++ b/src/lib/corelib/tools/settings.cpp
@@ -154,8 +154,8 @@ void Settings::fixupKeys(QStringList &keys) const
{
keys.sort();
keys.removeDuplicates();
- for (QStringList::Iterator it = keys.begin(); it != keys.end(); ++it)
- *it = externalRepresentation(*it);
+ for (auto &key : keys)
+ key = externalRepresentation(key);
}
} // namespace qbs
diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp
index 566668a56..b1b289a60 100644
--- a/src/lib/corelib/tools/setupprojectparameters.cpp
+++ b/src/lib/corelib/tools/setupprojectparameters.cpp
@@ -345,21 +345,19 @@ QVariantMap SetupProjectParameters::buildConfigurationTree() const
return d->buildConfigurationTree;
}
-static QVariantMap expandedBuildConfigurationInternal(const QString &settingsBaseDir,
- const QString &profileName, const QString &configurationName)
+static QVariantMap expandedBuildConfigurationInternal(const Profile &profile,
+ const QString &configurationName)
{
- Settings settings(settingsBaseDir);
QVariantMap buildConfig;
// (1) Values from profile, if given.
- if (!profileName.isEmpty() && profileName != Profile::fallbackName()) {
+ if (profile.exists() && profile.name() != Profile::fallbackName()) {
ErrorInfo err;
- const Profile profile(profileName, &settings);
const QStringList profileKeys = profile.allKeys(Profile::KeySelectionRecursive, &err);
if (err.hasError())
throw err;
if (profileKeys.isEmpty())
- throw ErrorInfo(Internal::Tr::tr("Unknown or empty profile '%1'.").arg(profileName));
+ throw ErrorInfo(Internal::Tr::tr("Unknown or empty profile '%1'.").arg(profile.name()));
for (const QString &profileKey : profileKeys) {
buildConfig.insert(profileKey, profile.value(profileKey, QVariant(), &err));
if (err.hasError())
@@ -375,11 +373,11 @@ static QVariantMap expandedBuildConfigurationInternal(const QString &settingsBas
}
-QVariantMap SetupProjectParameters::expandedBuildConfiguration(const QString &settingsBaseDir,
- const QString &profileName, const QString &configurationName, ErrorInfo *errorInfo)
+QVariantMap SetupProjectParameters::expandedBuildConfiguration(const Profile &profile,
+ const QString &configurationName, ErrorInfo *errorInfo)
{
try {
- return expandedBuildConfigurationInternal(settingsBaseDir, profileName, configurationName);
+ return expandedBuildConfigurationInternal(profile, configurationName);
} catch (const ErrorInfo &err) {
if (errorInfo)
*errorInfo = err;
@@ -401,8 +399,9 @@ QVariantMap SetupProjectParameters::expandedBuildConfiguration(const QString &se
ErrorInfo SetupProjectParameters::expandBuildConfiguration()
{
ErrorInfo err;
- QVariantMap expandedConfig = expandedBuildConfiguration(d->settingsBaseDir, topLevelProfile(),
- configurationName(), &err);
+ Settings settings(d->settingsBaseDir);
+ Profile profile(topLevelProfile(), &settings);
+ QVariantMap expandedConfig = expandedBuildConfiguration(profile, configurationName(), &err);
if (err.hasError())
return err;
if (d->buildConfiguration != expandedConfig) {
diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h
index 21dc99e0b..207aa9000 100644
--- a/src/lib/corelib/tools/setupprojectparameters.h
+++ b/src/lib/corelib/tools/setupprojectparameters.h
@@ -50,6 +50,7 @@
namespace qbs {
+class Profile;
class Settings;
namespace Internal { class SetupProjectParametersPrivate; }
@@ -93,8 +94,7 @@ public:
void setOverriddenValues(const QVariantMap &values);
QVariantMap overriddenValuesTree() const;
- static QVariantMap expandedBuildConfiguration(const QString &settingsBaseDir,
- const QString &profileName, const QString &configurationName, ErrorInfo *errorInfo = 0);
+ static QVariantMap expandedBuildConfiguration(const Profile &profile, const QString &configurationName, ErrorInfo *errorInfo = 0);
ErrorInfo expandBuildConfiguration();
QVariantMap buildConfiguration() const;
QVariantMap buildConfigurationTree() const;
diff --git a/src/lib/corelib/tools/shellutils.cpp b/src/lib/corelib/tools/shellutils.cpp
index f41f3e11a..4521e018c 100644
--- a/src/lib/corelib/tools/shellutils.cpp
+++ b/src/lib/corelib/tools/shellutils.cpp
@@ -152,6 +152,11 @@ QString shellQuote(const QString &arg, HostOsInfo::HostOs os)
return os == HostOsInfo::HostOsWindows ? shellQuoteWin(arg) : shellQuoteUnix(arg);
}
+std::string shellQuote(const std::string &arg, HostOsInfo::HostOs os)
+{
+ return shellQuote(QString::fromStdString(arg), os).toStdString();
+}
+
QString shellQuote(const QStringList &args, HostOsInfo::HostOs os)
{
QString result;
@@ -163,6 +168,21 @@ QString shellQuote(const QStringList &args, HostOsInfo::HostOs os)
return result;
}
+std::string shellQuote(const std::vector<std::string> &args, HostOsInfo::HostOs os)
+{
+ std::string result;
+ if (!args.empty()) {
+ auto it = args.cbegin();
+ const auto end = args.cend();
+ result += shellQuote(*it++, os);
+ for (; it != end; ++it) {
+ result.push_back(' ');
+ result.append(shellQuote(*it, os));
+ }
+ }
+ return result;
+}
+
QString shellQuote(const QString &program, const QStringList &args, HostOsInfo::HostOs os)
{
QString result = shellQuote(program, os);
@@ -177,11 +197,22 @@ void CommandLine::setProgram(const QString &program, bool raw)
m_isRawProgram = raw;
}
+void CommandLine::setProgram(const std::string &program, bool raw)
+{
+ m_program = QString::fromStdString(program);
+ m_isRawProgram = raw;
+}
+
void CommandLine::appendArgument(const QString &value)
{
m_arguments.push_back(value);
}
+void CommandLine::appendArgument(const std::string &value)
+{
+ m_arguments.push_back(QString::fromStdString(value));
+}
+
void CommandLine::appendArguments(const QList<QString> &args)
{
for (const QString &arg : args)
@@ -195,6 +226,11 @@ void CommandLine::appendRawArgument(const QString &value)
m_arguments.push_back(arg);
}
+void CommandLine::appendRawArgument(const std::string &value)
+{
+ appendRawArgument(QString::fromStdString(value));
+}
+
void CommandLine::appendPathArgument(const QString &value)
{
Argument arg(value);
diff --git a/src/lib/corelib/tools/shellutils.h b/src/lib/corelib/tools/shellutils.h
index 5d748152b..c148db4b8 100644
--- a/src/lib/corelib/tools/shellutils.h
+++ b/src/lib/corelib/tools/shellutils.h
@@ -51,9 +51,12 @@ namespace qbs {
namespace Internal {
QBS_EXPORT QString shellInterpreter(const QString &filePath);
+QBS_EXPORT std::string shellQuote(const std::string &arg, HostOsInfo::HostOs os = HostOsInfo::hostOs());
QBS_EXPORT QString shellQuote(const QString &arg, HostOsInfo::HostOs os = HostOsInfo::hostOs());
QBS_EXPORT QString shellQuote(const QStringList &args,
HostOsInfo::HostOs os = HostOsInfo::hostOs());
+QBS_EXPORT std::string shellQuote(const std::vector<std::string> &args,
+ HostOsInfo::HostOs os = HostOsInfo::hostOs());
QBS_EXPORT QString shellQuote(const QString &program, const QStringList &args,
HostOsInfo::HostOs os = HostOsInfo::hostOs());
@@ -61,9 +64,12 @@ class QBS_EXPORT CommandLine
{
public:
void setProgram(const QString &program, bool raw = false);
+ void setProgram(const std::string &program, bool raw = false);
void appendArgument(const QString &value);
+ void appendArgument(const std::string &value);
void appendArguments(const QList<QString> &args);
void appendRawArgument(const QString &value);
+ void appendRawArgument(const std::string &value);
void appendPathArgument(const QString &value);
void clearArguments();
QString toCommandLine(HostOsInfo::HostOs os = HostOsInfo::hostOs()) const;
diff --git a/src/lib/corelib/tools/stlutils.h b/src/lib/corelib/tools/stlutils.h
index 33e4be637..894c9b4a1 100644
--- a/src/lib/corelib/tools/stlutils.h
+++ b/src/lib/corelib/tools/stlutils.h
@@ -61,12 +61,26 @@ bool contains(const C &container, const typename C::value_type &v)
}
template <class C>
+bool containsKey(const C &container, const typename C::key_type &v)
+{
+ const auto &end = container.cend();
+ return container.find(v) != end;
+}
+
+template <class C>
C &operator<<(C &container, const typename C::value_type &v)
{
container.push_back(v);
return container;
}
+template <class C>
+C &operator<<(C &container, const C &other)
+{
+ container.insert(container.end(), other.cbegin(), other.cend());
+ return container;
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/tools/stringutils.h b/src/lib/corelib/tools/stringutils.h
new file mode 100644
index 000000000..374637b32
--- /dev/null
+++ b/src/lib/corelib/tools/stringutils.h
@@ -0,0 +1,135 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBS_STRINGUTILS_H
+#define QBS_STRINGUTILS_H
+
+#include <algorithm>
+#include <functional>
+#include <cctype>
+#include <locale>
+
+namespace qbs {
+namespace Internal {
+
+template <class C>
+typename C::value_type join(const C &container, const typename C::value_type &separator)
+{
+ typename C::value_type out;
+ if (!container.empty()) {
+ auto it = container.cbegin();
+ auto end = container.cend();
+ out.append(*it++);
+ for (; it != end; ++it) {
+ out.append(separator);
+ out.append(*it);
+ }
+ }
+ return out;
+}
+
+template <class C>
+typename C::value_type join(const C &container, typename C::value_type::value_type separator)
+{
+ typename C::value_type s;
+ s.push_back(separator);
+ return join(container, s);
+}
+
+static inline std::string trimmed(const std::string &s)
+{
+ // trim from start
+ static const auto ltrim = [](std::string &s) -> std::string & {
+ s.erase(s.begin(), std::find_if(s.begin(), s.end(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))));
+ return s;
+ };
+
+ // trim from end
+ static const auto rtrim = [](std::string &s) -> std::string & {
+ s.erase(std::find_if(s.rbegin(), s.rend(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
+ return s;
+ };
+
+ // trim from both ends
+ static const auto trim = [](std::string &s) -> std::string & {
+ return ltrim(rtrim(s));
+ };
+
+ std::string copy = s;
+ return trim(copy);
+}
+
+static inline bool startsWith(const std::string &subject, const std::string &s)
+{
+ if (s.size() <= subject.size())
+ return std::equal(s.begin(), s.end(), subject.begin());
+ return false;
+}
+
+static inline bool startsWith(const std::string &subject, char c)
+{
+ std::string s;
+ s.push_back(c);
+ return startsWith(subject, s);
+}
+
+static inline bool endsWith(const std::string &subject, const std::string &s)
+{
+ if (s.size() <= subject.size())
+ return std::equal(s.rbegin(), s.rend(), subject.rbegin());
+ return false;
+}
+
+static inline bool endsWith(const std::string &subject, char c)
+{
+ std::string s;
+ s.push_back(c);
+ return endsWith(subject, s);
+}
+
+} // namespace Internal
+} // namespace qbs
+
+#ifdef QT_CORE_LIB
+Q_DECLARE_METATYPE(std::string)
+#endif
+
+#endif // QBS_STRINGUTILS_H
diff --git a/src/lib/corelib/tools/tools.pri b/src/lib/corelib/tools/tools.pri
index df85224db..81d215559 100644
--- a/src/lib/corelib/tools/tools.pri
+++ b/src/lib/corelib/tools/tools.pri
@@ -14,6 +14,7 @@ HEADERS += \
$$PWD/filetime.h \
$$PWD/generateoptions.h \
$$PWD/id.h \
+ $$PWD/iosutils.h \
$$PWD/jsliterals.h \
$$PWD/launcherinterface.h \
$$PWD/launcherpackets.h \
@@ -38,6 +39,7 @@ HEADERS += \
$$PWD/qbsprocess.h \
$$PWD/shellutils.h \
$$PWD/stlutils.h \
+ $$PWD/stringutils.h \
$$PWD/toolchains.h \
$$PWD/hostosinfo.h \
$$PWD/buildoptions.h \
diff --git a/src/lib/qtprofilesetup/qtenvironment.h b/src/lib/qtprofilesetup/qtenvironment.h
index 6b52ab934..4747bb364 100644
--- a/src/lib/qtprofilesetup/qtenvironment.h
+++ b/src/lib/qtprofilesetup/qtenvironment.h
@@ -72,6 +72,8 @@ public:
QString windowsVersion;
QString macosVersion;
QString iosVersion;
+ QString tvosVersion;
+ QString watchosVersion;
QString androidVersion;
int qtMajorVersion;
int qtMinorVersion;
@@ -79,6 +81,7 @@ public:
Internal::Version msvcVersion;
bool staticBuild = false;
bool frameworkBuild = false;
+ bool hasQtQuickCompiler = false;
};
} // namespace qbs
diff --git a/src/lib/qtprofilesetup/qtmoduleinfo.cpp b/src/lib/qtprofilesetup/qtmoduleinfo.cpp
index d0a4f4717..28152ddd8 100644
--- a/src/lib/qtprofilesetup/qtmoduleinfo.cpp
+++ b/src/lib/qtprofilesetup/qtmoduleinfo.cpp
@@ -447,16 +447,14 @@ QList<QtModuleInfo> allQt4Modules(const QtEnvironment &qtEnvironment)
modules << phonon;
// Set up include paths that haven't been set up before this point.
- for (QList<QtModuleInfo>::iterator it = modules.begin(); it != modules.end(); ++it) {
- QtModuleInfo &module = *it;
+ for (auto &module : modules) {
if (!module.includePaths.isEmpty())
continue;
module.includePaths = module.qt4ModuleIncludePaths(qtEnvironment);
}
// Set up compiler defines haven't been set up before this point.
- for (QList<QtModuleInfo>::iterator it = modules.begin(); it != modules.end(); ++it) {
- QtModuleInfo &module = *it;
+ for (auto &module : modules) {
if (!module.compilerDefines.isEmpty())
continue;
module.compilerDefines
diff --git a/src/lib/qtprofilesetup/qtprofilesetup.cpp b/src/lib/qtprofilesetup/qtprofilesetup.cpp
index 7ad837105..8e401f2bb 100644
--- a/src/lib/qtprofilesetup/qtprofilesetup.cpp
+++ b/src/lib/qtprofilesetup/qtprofilesetup.cpp
@@ -44,6 +44,7 @@
#include <logging/translator.h>
#include <tools/architectures.h>
#include <tools/error.h>
+#include <tools/hostosinfo.h>
#include <tools/jsliterals.h>
#include <tools/profile.h>
#include <tools/settings.h>
@@ -165,6 +166,7 @@ static void replaceSpecialValues(QByteArray *content, const Profile &profile,
content->replace("@pluginPath@", utf8JSLiteral(qtEnvironment.pluginPath));
content->replace("@incPath@", utf8JSLiteral(qtEnvironment.includePath));
content->replace("@docPath@", utf8JSLiteral(qtEnvironment.documentationPath));
+ content->replace("@mkspecName@", utf8JSLiteral(qtEnvironment.mkspecName));
content->replace("@mkspecPath@", utf8JSLiteral(qtEnvironment.mkspecPath));
content->replace("@version@", utf8JSLiteral(qtEnvironment.qtVersion));
content->replace("@libInfix@", utf8JSLiteral(qtEnvironment.qtLibInfix));
@@ -197,6 +199,8 @@ static void replaceSpecialValues(QByteArray *content, const Profile &profile,
content->replace("@minWinVersion@", minVersionJsString(qtEnvironment.windowsVersion));
content->replace("@minMacVersion@", minVersionJsString(qtEnvironment.macosVersion));
content->replace("@minIosVersion@", minVersionJsString(qtEnvironment.iosVersion));
+ content->replace("@minTvosVersion@", minVersionJsString(qtEnvironment.tvosVersion));
+ content->replace("@minWatchosVersion@", minVersionJsString(qtEnvironment.watchosVersion));
content->replace("@minAndroidVersion@", minVersionJsString(qtEnvironment.androidVersion));
QByteArray propertiesString;
@@ -219,6 +223,11 @@ static void replaceSpecialValues(QByteArray *content, const Profile &profile,
s << indent << "property string qmlImportsPath: "
<< pathToJSLiteral(qtEnvironment.qmlImportPath);
+ if (module.qbsName == QLatin1String("quick")) {
+ s << endl << indent << "property bool compilerAvailable: "
+ << toJSLiteral(qtEnvironment.hasQtQuickCompiler);
+ }
+
const QByteArray baIndent(4, ' ');
compilerDefines = "{\n"
+ baIndent + baIndent + "var result = " + compilerDefines + ";\n"
@@ -313,8 +322,19 @@ static void createModules(Profile &profile, Settings *settings,
moduleTemplateFileName = QLatin1String("qml.qbs");
copyTemplateFile(QLatin1String("qml.js"), qbsQtModuleDir, profile, qtEnvironment,
&allFiles);
+ const QString qmlcacheStr = QStringLiteral("qmlcache");
+ if (QFileInfo::exists(HostOsInfo::appendExecutableSuffix(
+ qtEnvironment.binaryPath + QStringLiteral("/qmlcachegen")))) {
+ copyTemplateFile(qmlcacheStr + QStringLiteral(".qbs"),
+ qbsQtModuleBaseDir + QLatin1Char('/') + qmlcacheStr, profile,
+ qtEnvironment, &allFiles);
+ }
} else if (module.qbsName == QLatin1String("phonon")) {
moduleTemplateFileName = QLatin1String("phonon.qbs");
+ } else if (module.qbsName == QLatin1String("quick")) {
+ moduleTemplateFileName = QLatin1String("quick.qbs");
+ copyTemplateFile(QLatin1String("quick.js"), qbsQtModuleDir, profile,
+ qtEnvironment, &allFiles);
} else if (module.isPlugin) {
moduleTemplateFileName = QLatin1String("plugin.qbs");
} else {
@@ -339,7 +359,7 @@ static void createModules(Profile &profile, Settings *settings,
static QString guessMinimumWindowsVersion(const QtEnvironment &qt)
{
if (qt.mkspecName.startsWith(QLatin1String("winrt-")))
- return QLatin1String("6.2");
+ return QLatin1String("10.0");
if (!qt.mkspecName.startsWith(QLatin1String("win32-")))
return QString();
@@ -422,7 +442,42 @@ void doSetupQtProfile(const QString &profileName, Settings *settings,
qtEnvironment.entryPointLibsRelease = fillEntryPointLibs(qtEnvironment, qtVersion, false);
} else if (qtEnvironment.mkspecPath.contains(QLatin1String("macx"))) {
if (qtEnvironment.qtMajorVersion >= 5) {
- qtEnvironment.macosVersion = QLatin1String("10.6");
+ QFile qmakeConf(qtEnvironment.mkspecPath + QStringLiteral("/qmake.conf"));
+ if (qmakeConf.open(QIODevice::ReadOnly)) {
+ static const std::regex re(
+ "^QMAKE_(MACOSX|IOS|TVOS|WATCHOS)_DEPLOYMENT_TARGET\\s*=\\s*(.*)\\s*$");
+ while (!qmakeConf.atEnd()) {
+ std::smatch match;
+ const auto line = qmakeConf.readLine().trimmed().toStdString();
+ if (std::regex_match(line, match, re)) {
+ const auto platform = QString::fromStdString(match[1]);
+ const auto version = QString::fromStdString(match[2]);
+ if (platform == QStringLiteral("MACOSX"))
+ qtEnvironment.macosVersion = version;
+ else if (platform == QStringLiteral("IOS"))
+ qtEnvironment.iosVersion = version;
+ else if (platform == QStringLiteral("TVOS"))
+ qtEnvironment.tvosVersion = version;
+ else if (platform == QStringLiteral("WATCHOS"))
+ qtEnvironment.watchosVersion = version;
+ }
+ }
+ }
+
+ const bool isMac = qtEnvironment.mkspecName != QStringLiteral("macx-ios-clang")
+ && qtEnvironment.mkspecName != QStringLiteral("macx-tvos-clang")
+ && qtEnvironment.mkspecName != QStringLiteral("macx-watchos-clang");
+ if (isMac) {
+ // Qt 5.0.x placed the minimum version in a different file
+ if (qtEnvironment.macosVersion.isEmpty()) {
+ qtEnvironment.macosVersion = QLatin1String("10.6");
+ }
+
+ // If we're using C++11 with libc++, make sure the deployment target is >= 10.7
+ if (Version::fromString(qtEnvironment.macosVersion) < Version(10, 7)
+ && qtEnvironment.qtConfigItems.contains(QLatin1String("c++11")))
+ qtEnvironment.macosVersion = QLatin1String("10.7");
+ }
} else if (qtEnvironment.qtMajorVersion == 4 && qtEnvironment.qtMinorVersion >= 6) {
QDir qconfigDir;
if (qtEnvironment.frameworkBuild) {
@@ -457,27 +512,19 @@ void doSetupQtProfile(const QString &profileName, Settings *settings,
"whether Qt is using Cocoa or Carbon"));
}
}
-
- if (qtEnvironment.qtConfigItems.contains(QLatin1String("c++11")))
- qtEnvironment.macosVersion = QLatin1String("10.7");
- }
-
- if (qtEnvironment.mkspecPath.contains(QLatin1String("ios"))
- && qtEnvironment.qtMajorVersion >= 5) {
- qtEnvironment.iosVersion = QLatin1String("5.0");
- }
-
- if (qtEnvironment.mkspecPath.contains(QLatin1String("android"))) {
+ } else if (qtEnvironment.mkspecPath.contains(QLatin1String("android"))) {
if (qtEnvironment.qtMajorVersion >= 5)
qtEnvironment.androidVersion = QLatin1String("2.3");
else if (qtEnvironment.qtMajorVersion == 4 && qtEnvironment.qtMinorVersion >= 8)
qtEnvironment.androidVersion = QLatin1String("1.6"); // Necessitas
}
- // ### TODO: wince, winphone, blackberry
-
Profile profile(profileName, settings);
profile.removeProfile();
+ if (QFileInfo::exists(qtEnvironment.mkspecBasePath
+ + QStringLiteral("/features/qtquickcompiler.prf"))) {
+ qtEnvironment.hasQtQuickCompiler = true;
+ }
createModules(profile, settings, qtEnvironment);
}
diff --git a/src/lib/qtprofilesetup/templates.qrc b/src/lib/qtprofilesetup/templates.qrc
index d65582a1c..5257eccec 100644
--- a/src/lib/qtprofilesetup/templates.qrc
+++ b/src/lib/qtprofilesetup/templates.qrc
@@ -14,5 +14,8 @@
<file>templates/scxml.qbs</file>
<file>templates/qml.qbs</file>
<file>templates/qml.js</file>
+ <file>templates/qmlcache.qbs</file>
+ <file>templates/quick.js</file>
+ <file>templates/quick.qbs</file>
</qresource>
</RCC>
diff --git a/src/lib/qtprofilesetup/templates/core.qbs b/src/lib/qtprofilesetup/templates/core.qbs
index 9dd56cfab..399c6edd9 100644
--- a/src/lib/qtprofilesetup/templates/core.qbs
+++ b/src/lib/qtprofilesetup/templates/core.qbs
@@ -23,6 +23,7 @@ Module {
property path incPath: @incPath@
property path libPath: @libPath@
property path pluginPath: @pluginPath@
+ property string mkspecName: @mkspecName@
property path mkspecPath: @mkspecPath@
property string mocName: "moc"
property stringList mocFlags: []
@@ -81,7 +82,6 @@ Module {
// These are deliberately not path types
// We don't want to resolve them against the source directory
property string generatedHeadersDir: product.buildDirectory + "/qt.headers"
- property string generatedFilesDir: generatedHeadersDir // TODO: Remove in 1.8
property string qdocOutputDir: product.buildDirectory + "/qdoc_html"
property string qmDir: product.destinationDirectory
property string qmBaseName: product.targetName
@@ -92,6 +92,8 @@ Module {
? "_qt_main_wrapper"
: undefined
cpp.cxxLanguageVersion: Utilities.versionCompare(version, "5.7.0") >= 0 ? "c++11" : original
+ cpp.enableCompilerDefinesByLanguage: ["cpp"].concat(
+ qbs.targetOS.contains("darwin") ? ["objcpp"] : [])
cpp.defines: {
var defines = @defines@;
// ### QT_NO_DEBUG must be added if the current build variant is derived
@@ -174,8 +176,15 @@ Module {
cpp.minimumWindowsVersion: @minWinVersion@
cpp.minimumMacosVersion: @minMacVersion@
cpp.minimumIosVersion: @minIosVersion@
+ cpp.minimumTvosVersion: @minTvosVersion@
+ cpp.minimumWatchosVersion: @minWatchosVersion@
cpp.minimumAndroidVersion: @minAndroidVersion@
+ // Universal Windows Platform support
+ cpp.windowsApiFamily: mkspecName.startsWith("winrt-") ? "pc" : undefined
+ cpp.windowsApiFamilyAdditionalPartitions: mkspecPath.startsWith("winrt-") ? ["phone"] : undefined
+ cpp.requireAppContainer: mkspecName.startsWith("winrt-")
+
additionalProductTypes: ["qm"]
validate: {
diff --git a/src/lib/qtprofilesetup/templates/moc.js b/src/lib/qtprofilesetup/templates/moc.js
index ced487d6e..a9fddbc8a 100644
--- a/src/lib/qtprofilesetup/templates/moc.js
+++ b/src/lib/qtprofilesetup/templates/moc.js
@@ -28,9 +28,17 @@
**
****************************************************************************/
+var ModUtils = require("qbs.ModUtils");
+
function args(product, input, outputFileName)
{
- var defines = product.cpp.compilerDefines;
+ var defines = product.cpp.compilerDefinesByLanguage;
+ if (input.fileTags.contains("objcpp"))
+ defines = ModUtils.flattenDictionary(defines["objcpp"]) || [];
+ else if (input.fileTags.contains("cpp"))
+ defines = ModUtils.flattenDictionary(defines["cpp"]) || [];
+ else
+ defines = [];
defines = defines.uniqueConcat(product.cpp.platformDefines);
defines = defines.uniqueConcat(input.cpp.defines);
var includePaths = input.cpp.includePaths;
diff --git a/src/lib/qtprofilesetup/templates/qml.qbs b/src/lib/qtprofilesetup/templates/qml.qbs
index a88bcbd6a..d161f70e4 100644
--- a/src/lib/qtprofilesetup/templates/qml.qbs
+++ b/src/lib/qtprofilesetup/templates/qml.qbs
@@ -11,6 +11,14 @@ QtModule {
property string qmlImportScannerFilePath: Qt.core.binPath + '/' + qmlImportScannerName
property string qmlPath: @qmlPath@
+ property bool generateCacheFiles: false
+ Depends { name: "Qt.qmlcache"; condition: generateCacheFiles; required: false }
+ readonly property bool cachingEnabled: generateCacheFiles && Qt.qmlcache.present
+ property string qmlCacheGenPath
+ Qt.qmlcache.qmlCacheGenPath: qmlCacheGenPath || original
+ property string cacheFilesInstallDir
+ Qt.qmlcache.installDir: cacheFilesInstallDir || original
+
readonly property string pluginListFilePathDebug: product.buildDirectory + "/plugins.list.d"
readonly property string pluginListFilePathRelease: product.buildDirectory + "/plugins.list"
@@ -40,6 +48,11 @@ QtModule {
fileTags: ["qt.qml.qml"]
}
+ FileTagger {
+ patterns: ["*.js"]
+ fileTags: ["qt.qml.js"]
+ }
+
Rule {
condition: isStaticLibrary
multiplex: true
diff --git a/src/lib/qtprofilesetup/templates/qmlcache.qbs b/src/lib/qtprofilesetup/templates/qmlcache.qbs
new file mode 100644
index 000000000..246486c53
--- /dev/null
+++ b/src/lib/qtprofilesetup/templates/qmlcache.qbs
@@ -0,0 +1,67 @@
+import qbs.FileInfo
+import qbs.Process
+
+Module {
+ additionalProductTypes: ["qt.qml.qmlc", "qt.qml.jsc"]
+ validate: qmlcachegenProbe.found
+ property string qmlCacheGenPath: FileInfo.joinPaths(Qt.core.binPath, "qmlcachegen")
+ + (qbs.hostOS.contains("windows") ? ".exe" : "")
+ property string installDir
+
+ readonly property stringList _targetArgs: {
+ function translateArch(arch) {
+ if (arch === "x86")
+ return "i386";
+ if (arch.startsWith("armv"))
+ return "arm";
+ return arch;
+ }
+
+ var args = ["--target-architecture", translateArch(qbs.architecture)];
+ return args;
+ }
+
+ Depends { name: "Qt.core" }
+ Probe {
+ id: qmlcachegenProbe
+ configure: {
+ var process = new Process();
+ found = false;
+ try {
+ found = process.exec(qmlCacheGenPath,
+ _targetArgs.concat("--check-if-supported")) == 0;
+ if (!found) {
+ var msg = "QML cache generation was requested but is unsupported on "
+ + "architecture '" + qbs.architecture + "'.";
+ console.warn(msg);
+ }
+ } finally {
+ process.close();
+ }
+ }
+ }
+ Rule {
+ condition: qmlcachegenProbe.found
+ inputs: ["qt.qml.qml", "qt.qml.js"]
+ outputArtifacts: [{
+ filePath: input.fileName + 'c',
+ fileTags: input.fileTags.filter(function(tag) {
+ return tag === "qt.qml.qml" || tag === "qt.qml.js";
+ })[0] + 'c'
+ }]
+ outputFileTags: ["qt.qml.qmlc", "qt.qml.jsc"]
+ prepare: {
+ var args = input.Qt.qmlcache._targetArgs.concat(input.filePath, "-o", output.filePath);
+ var cmd = new Command(input.Qt.qmlcache.qmlCacheGenPath, args);
+ cmd.description = "precompiling " + input.fileName;
+ cmd.highlight = "compiler";
+ return cmd;
+ }
+ }
+ Group {
+ condition: product.Qt.qmlcache.installDir !== undefined
+ fileTagsFilter: product.Qt.qmlcache.additionalProductTypes
+ qbs.install: true
+ qbs.installDir: product.Qt.qmlcache.installDir
+ }
+}
diff --git a/src/lib/qtprofilesetup/templates/quick.js b/src/lib/qtprofilesetup/templates/quick.js
new file mode 100644
index 000000000..0ab10606b
--- /dev/null
+++ b/src/lib/qtprofilesetup/templates/quick.js
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+var FileInfo = require("qbs.FileInfo");
+var Process = require("qbs.Process");
+
+function scanQrc(qrcFilePath) {
+ var absInputDir = FileInfo.path(qrcFilePath);
+ var result = [];
+ var process = new Process();
+ try {
+ var rcc = FileInfo.joinPaths(product.Qt.core.binPath, 'rcc' + product.cpp.executableSuffix);
+ var exitCode = process.exec(rcc, ["--list", qrcFilePath], true);
+ for (;;) {
+ var line = process.readLine();
+ if (!line)
+ break;
+ line = line.trim();
+ line = FileInfo.relativePath(absInputDir, line);
+ result.push(line);
+ }
+ } finally {
+ process.close();
+ }
+ return result;
+}
+
+function qtQuickCompilerOutputName(filePath) {
+ var str = filePath.replace('/', '_');
+ var i = str.lastIndexOf('.');
+ if (i != -1)
+ str = str.substr(0, i) + '_' + str.substr(i + 1);
+ str += ".cpp";
+ return str;
+}
+
+function qtQuickResourceFileOutputName(fileName) {
+ return fileName.replace(/\.qrc$/, "_qtquickcompiler.qrc");
+}
+
+function contentFromQrc(qrcFilePath) {
+ var filesInQrc = scanQrc(qrcFilePath);
+ var qmlJsFiles = filesInQrc.filter(function (filePath) {
+ return (/\.(js|qml)$/).test(filePath);
+ } );
+ var content = {};
+ if (filesInQrc.length - qmlJsFiles.length > 0) {
+ content.newQrcFileName = qtQuickResourceFileOutputName(input.fileName);
+ }
+ content.qmlJsFiles = qmlJsFiles.map(function (filePath) {
+ return {
+ input: filePath,
+ output: qtQuickCompilerOutputName(filePath)
+ };
+ });
+ return content;
+}
diff --git a/src/lib/qtprofilesetup/templates/quick.qbs b/src/lib/qtprofilesetup/templates/quick.qbs
new file mode 100644
index 000000000..5358a684f
--- /dev/null
+++ b/src/lib/qtprofilesetup/templates/quick.qbs
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+import qbs
+import qbs.Process
+import qbs.FileInfo
+import qbs.TextFile
+import '../QtModule.qbs' as QtModule
+import 'quick.js' as QC
+
+QtModule {
+ qtModuleName: @name@
+ Depends { name: "Qt"; submodules: @dependencies@.concat("qml-private") }
+
+ hasLibrary: @has_library@
+ architecture: @arch@
+ staticLibsDebug: @staticLibsDebug@
+ staticLibsRelease: @staticLibsRelease@
+ dynamicLibsDebug: @dynamicLibsDebug@
+ dynamicLibsRelease: @dynamicLibsRelease@
+ linkerFlagsDebug: @linkerFlagsDebug@
+ linkerFlagsRelease: @linkerFlagsRelease@
+ frameworksDebug: @frameworksDebug@
+ frameworksRelease: @frameworksRelease@
+ frameworkPathsDebug: @frameworkPathsDebug@
+ frameworkPathsRelease: @frameworkPathsRelease@
+ libNameForLinkerDebug: @libNameForLinkerDebug@
+ libNameForLinkerRelease: @libNameForLinkerRelease@
+ libFilePathDebug: @libFilePathDebug@
+ libFilePathRelease: @libFilePathRelease@
+ cpp.defines: @defines@
+ cpp.includePaths: @includes@
+ cpp.libraryPaths: @libraryPaths@
+ @special_properties@
+ Scanner {
+ condition: compilerAvailable
+ inputs: 'qt.quick.qrc'
+ searchPaths: [FileInfo.path(input.filePath)]
+ scan: QC.scanQrc(input.filePath)
+ }
+
+ FileTagger {
+ condition: compilerAvailable
+ patterns: "*.qrc"
+ fileTags: ["qt.quick.qrc"]
+ priority: 100
+ }
+
+ Rule {
+ condition: compilerAvailable
+ inputs: ["qt.quick.qrc"]
+ Artifact {
+ filePath: input.fileName + ".json"
+ fileTags: ["qt.quick.qrcinfo"]
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.silent = true;
+ cmd.sourceCode = function() {
+ var content = QC.contentFromQrc(input.filePath);
+ content.qrcFilePath = input.filePath;
+
+ var tf = new TextFile(output.filePath, TextFile.WriteOnly);
+ tf.write(JSON.stringify(content));
+ tf.close();
+ }
+ return cmd;
+ }
+ }
+
+ Rule {
+ condition: compilerAvailable
+ inputs: ["qt.quick.qrcinfo"]
+ outputFileTags: ["cpp", "qrc"]
+ multiplex: true
+ outputArtifacts: {
+ var infos = [];
+ inputs["qt.quick.qrcinfo"].forEach(function (input) {
+ var tf = new TextFile(input.filePath, TextFile.ReadOnly);
+ infos.push(JSON.parse(tf.readAll()));
+ tf.close();
+ });
+
+ var result = [];
+ infos.forEach(function (info) {
+ if (info.newQrcFileName) {
+ result.push({
+ filePath: info.newQrcFileName,
+ fileTags: ["qrc"]
+ });
+ }
+ info.qmlJsFiles.forEach(function (qmlJsFile) {
+ result.push({
+ filePath: qmlJsFile.output,
+ fileTags: ["cpp"]
+ });
+ });
+ });
+ result.push({
+ filePath: "qtquickcompiler_loader.cpp",
+ fileTags: ["cpp"]
+ });
+ return result;
+ }
+ prepare: {
+ var infos = [];
+ inputs["qt.quick.qrcinfo"].forEach(function (input) {
+ var tf = new TextFile(input.filePath, TextFile.ReadOnly);
+ infos.push(JSON.parse(tf.readAll()));
+ tf.close();
+ });
+
+ var cmds = [];
+ var qmlCompiler = FileInfo.joinPaths(product.Qt.core.binPath,
+ "qtquickcompiler" + product.cpp.executableSuffix);
+ var cmd;
+ var loaderFlags = [];
+
+ function findOutput(fileName) {
+ for (var k in outputs) {
+ for (var i in outputs[k]) {
+ if (outputs[k][i].fileName === fileName)
+ return outputs[k][i];
+ }
+ }
+ throw new Error("Qt Quick compiler rule: Cannot find output artifact "
+ + fileName + ".");
+ }
+
+ infos.forEach(function (info) {
+ if (info.newQrcFileName) {
+ loaderFlags.push("--resource-file-mapping="
+ + FileInfo.fileName(info.qrcFilePath)
+ + ":" + info.newQrcFileName);
+ cmd = new Command(qmlCompiler, ["--filter-resource-file",
+ info.qrcFilePath,
+ findOutput(info.newQrcFileName).filePath]);
+ cmd.description = "generating " + info.newQrcFileName;
+ cmds.push(cmd);
+ } else {
+ loaderFlags.push("--resource-file-mapping=" + info.qrcFilePath);
+ }
+ info.qmlJsFiles.forEach(function (qmlJsFile) {
+ cmd = new Command(qmlCompiler, [
+ "--resource=" + info.qrcFilePath,
+ qmlJsFile.input,
+ findOutput(qmlJsFile.output).filePath]);
+ cmd.description = "generating " + qmlJsFile.output;
+ cmd.workingDirectory = FileInfo.path(info.qrcFilePath);
+ cmds.push(cmd);
+ });
+ });
+
+ cmd = new Command(qmlCompiler, loaderFlags.concat(
+ infos.map(function (info) { return info.qrcFilePath; }),
+ findOutput("qtquickcompiler_loader.cpp").filePath
+ ));
+ cmd.description = "generating loader source";
+ cmds.push(cmd);
+ return cmds;
+ }
+ }
+}
diff --git a/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs b/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs
index c641296ad..db554f2c7 100644
--- a/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs
+++ b/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs
@@ -10,6 +10,11 @@ QbsProduct {
Depends { name: "Qt.network" }
+ Properties {
+ condition: qbs.targetOS.contains("macos") && qbsbuildconfig.enableRPath
+ cpp.rpaths: ["@loader_path/../../" + qbsbuildconfig.libDirName]
+ }
+
cpp.cxxLanguageVersion: "c++11"
cpp.includePaths: base.concat(pathToProtocolSources)
diff --git a/src/packages/archive/archive.qbs b/src/packages/archive/archive.qbs
index 589f39875..166ecdd27 100644
--- a/src/packages/archive/archive.qbs
+++ b/src/packages/archive/archive.qbs
@@ -4,7 +4,7 @@ import qbs.ModUtils
import qbs.Process
import qbs.TextFile
-Product {
+QbsProduct {
Depends { name: "qbs-config" }
Depends { name: "qbs-config-ui" }
Depends { name: "qbs-qmltypes" }
@@ -13,7 +13,6 @@ Product {
Depends { name: "qbs-setup-toolchains" }
Depends { name: "qbs_app" }
Depends { name: "qbs_processlauncher" }
- Depends { name: "qbsversion" }
Depends { name: "qbscore" }
Depends { name: "qbsqtprofilesetup" }
Depends { name: "qbs_cpp_scanner" }
@@ -22,20 +21,14 @@ Product {
Depends { name: "qbs resources" }
Depends { name: "archiver" }
- Depends { name: "Qt.core" }
-
- readonly property bool hasQt56: {
- if (Qt.core.versionMajor === 5)
- return Qt.core.versionMinor >= 6;
- return Qt.core.versionMajor > 5;
- }
property stringList windeployqtArgs: [
"--no-svg",
"--no-system-d3d-compiler",
"--no-angle",
"--no-compiler-runtime",
- ].concat(hasQt56 ? ["--no-opengl-sw"] : [])
+ "--no-opengl-sw",
+ ]
// List of path prefixes to be excluded from the generated archive
property stringList excludedPathPrefixes: [
@@ -47,21 +40,97 @@ Product {
]
property bool includeTopLevelDir: false
- condition: qbs.targetOS.contains("windows")
+ condition: qbs.targetOS.containsAny(["windows", "macos"])
builtByDefault: false
name: "qbs archive"
type: ["archiver.archive"]
- targetName: "qbs-windows-" + qbs.architecture + "-" + qbsversion.version
+ targetName: "qbs-" + qbs.targetOS[0] + "-" + qbs.architecture + "-" + qbsversion.version
destinationDirectory: project.buildDirectory
- archiver.type: "zip"
+ archiver.type: qbs.targetOS.contains("windows") ? "zip" : "tar"
Properties {
condition: includeTopLevelDir
archiver.workingDirectory: qbs.installRoot + "/.."
}
archiver.workingDirectory: qbs.installRoot
+ Group {
+ name: "qt.conf"
+ files: ["qt.conf"]
+ qbs.install: true
+ qbs.installDir: qbsbuildconfig.appInstallDir
+ }
+
+ Group {
+ name: "Licenses"
+ prefix: "../../../"
+ files: [
+ "LGPL_EXCEPTION.txt",
+ "LICENSE.LGPLv3",
+ "LICENSE.LGPLv21",
+ ]
+ qbs.install: true
+ qbs.installDir: "share/doc/qbs"
+ }
+
+ Group {
+ condition: qbs.targetOS.contains("macos")
+ prefix: Qt.core.libPath + "/"
+ name: "Qt libraries"
+ files: {
+ if (Qt.core.frameworkBuild) {
+ return [
+ "QtCore.framework/**",
+ "QtGui.framework/**",
+ "QtNetwork.framework/**",
+ "QtPrintSupport.framework/**",
+ "QtScript.framework/**",
+ "QtWidgets.framework/**",
+ "QtXml.framework/**",
+ ];
+ } else if (!Qt.core.staticBuild) {
+ return [
+ "libQt5Core*.dylib",
+ "libQt5Gui*.dylib",
+ "libQt5Network*.dylib",
+ "libQt5PrintSupport*.dylib",
+ "libQt5Script*.dylib",
+ "libQt5Widgets*.dylib",
+ "libQt5Xml*.dylib",
+ ];
+ }
+ return [];
+ }
+
+ excludeFiles: [
+ "**/*.prl",
+ "**/*_debug*",
+ ].concat(!qbsbuildconfig.installApiHeaders ? ["**/Headers", "**/Headers/**"] : [])
+
+ qbs.install: true
+ qbs.installDir: qbsbuildconfig.libInstallDir
+ qbs.installSourceBase: prefix
+ }
+
+ Group {
+ condition: qbs.targetOS.contains("macos")
+ prefix: Qt.core.pluginPath + "/"
+ name: "Qt platform plugins"
+ files: [
+ "platforms/libq*.dylib",
+ ]
+
+ excludeFiles: [
+ "**/*_debug.dylib",
+ ]
+
+ qbs.install: true
+ qbs.installDir: "plugins"
+ qbs.installSourceBase: prefix
+ }
+
Rule {
+ condition: qbs.targetOS.contains("windows")
multiplex: true
inputsFromDependencies: ["installable"]
@@ -112,7 +181,7 @@ Product {
Rule {
multiplex: true
- inputs: ["dependencies.json"]
+ inputs: ["dependencies.json", "installable"]
inputsFromDependencies: ["installable"]
Artifact {
@@ -131,7 +200,7 @@ Product {
cmd.baseDirectory = product.moduleProperty("archiver", "workingDirectory");
cmd.sourceCode = function() {
var tf;
- for (var i = 0; i < inputs["dependencies.json"].length; ++i) {
+ for (var i = 0; i < (inputs["dependencies.json"] || []).length; ++i) {
try {
tf = new TextFile(inputs["dependencies.json"][i].filePath,
TextFile.ReadOnly);
diff --git a/src/packages/archive/qt.conf b/src/packages/archive/qt.conf
new file mode 100644
index 000000000..7963d6644
--- /dev/null
+++ b/src/packages/archive/qt.conf
@@ -0,0 +1,2 @@
+[Paths]
+Prefix = ..
diff --git a/src/packages/packages.qbs b/src/packages/packages.qbs
index 3b3a7bd1d..12ca8b041 100644
--- a/src/packages/packages.qbs
+++ b/src/packages/packages.qbs
@@ -12,14 +12,5 @@ Project {
Depends { name: "qbs chocolatey"; required: false }
name: "dist"
builtByDefault: false
-
- Group {
- name: "Scripts"
- prefix: "../../scripts/"
- files: [
- "make-release-archives.sh",
- "make-release-archives.bat",
- ]
- }
}
}
diff --git a/src/plugins/generator/visualstudio/io/msbuildprojectwriter.cpp b/src/plugins/generator/visualstudio/io/msbuildprojectwriter.cpp
index f912f3a19..e18618fcd 100644
--- a/src/plugins/generator/visualstudio/io/msbuildprojectwriter.cpp
+++ b/src/plugins/generator/visualstudio/io/msbuildprojectwriter.cpp
@@ -51,6 +51,8 @@ static const QString kMSBuildSchemaURI =
class MSBuildProjectWriterPrivate : public IMSBuildNodeVisitor
{
public:
+ std::ostream *device;
+ QByteArray buffer;
QScopedPointer<QXmlStreamWriter> writer;
void visitStart(const MSBuildImport *import) override;
@@ -81,10 +83,11 @@ public:
void visitEnd(const MSBuildPropertyGroup *propertyGroup) override;
};
-MSBuildProjectWriter::MSBuildProjectWriter(QIODevice *device)
+MSBuildProjectWriter::MSBuildProjectWriter(std::ostream *device)
: d(new MSBuildProjectWriterPrivate)
{
- d->writer.reset(new QXmlStreamWriter(device));
+ d->device = device;
+ d->writer.reset(new QXmlStreamWriter(&d->buffer));
d->writer->setAutoFormatting(true);
}
@@ -95,10 +98,14 @@ MSBuildProjectWriter::~MSBuildProjectWriter()
bool MSBuildProjectWriter::write(const MSBuildProject *project)
{
+ d->buffer.clear();
d->writer->writeStartDocument();
project->accept(d);
d->writer->writeEndDocument();
- return !d->writer->hasError();
+ if (d->writer->hasError())
+ return false;
+ d->device->write(&*std::begin(d->buffer), d->buffer.size());
+ return d->device->good();
}
void MSBuildProjectWriterPrivate::visitStart(const MSBuildImport *import)
diff --git a/src/plugins/generator/visualstudio/io/msbuildprojectwriter.h b/src/plugins/generator/visualstudio/io/msbuildprojectwriter.h
index c70889971..cfe6beb7b 100644
--- a/src/plugins/generator/visualstudio/io/msbuildprojectwriter.h
+++ b/src/plugins/generator/visualstudio/io/msbuildprojectwriter.h
@@ -31,7 +31,9 @@
#ifndef MSBUILDPROJECTWRITER_H
#define MSBUILDPROJECTWRITER_H
-#include <QtCore/qiodevice.h>
+#include <ostream>
+
+#include <QtCore/qglobal.h>
namespace qbs {
@@ -42,7 +44,7 @@ class MSBuildProjectWriter
{
Q_DISABLE_COPY(MSBuildProjectWriter)
public:
- explicit MSBuildProjectWriter(QIODevice *device);
+ explicit MSBuildProjectWriter(std::ostream *device);
~MSBuildProjectWriter();
bool write(const MSBuildProject *project);
diff --git a/src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.cpp b/src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.cpp
index 99180e2e2..d55a8cbb9 100644
--- a/src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.cpp
+++ b/src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.cpp
@@ -37,26 +37,28 @@
#include <tools/hostosinfo.h>
#include <tools/pathutils.h>
+#include <tools/stlutils.h>
#include <tools/visualstudioversioninfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
-#include <QtCore/qtextstream.h>
#include <QtCore/quuid.h>
#include <vector>
namespace qbs {
+using namespace Internal;
+
class VisualStudioSolutionWriterPrivate
{
public:
- QIODevice *device;
- QString baseDir;
+ std::ostream *device;
+ std::string baseDir;
};
-VisualStudioSolutionWriter::VisualStudioSolutionWriter(QIODevice *device)
+VisualStudioSolutionWriter::VisualStudioSolutionWriter(std::ostream *device)
: d(new VisualStudioSolutionWriterPrivate)
{
d->device = device;
@@ -66,81 +68,99 @@ VisualStudioSolutionWriter::~VisualStudioSolutionWriter()
{
}
-QString VisualStudioSolutionWriter::projectBaseDirectory() const
+std::string VisualStudioSolutionWriter::projectBaseDirectory() const
{
return d->baseDir;
}
-void VisualStudioSolutionWriter::setProjectBaseDirectory(const QString &dir)
+void VisualStudioSolutionWriter::setProjectBaseDirectory(const std::string &dir)
{
d->baseDir = dir;
}
bool VisualStudioSolutionWriter::write(const VisualStudioSolution *solution)
{
- QTextStream out(d->device);
- out << QString(QLatin1String("Microsoft Visual Studio Solution File, "
- "Format Version %1\n"
- "# Visual Studio %2\n"))
- .arg(solution->versionInfo().solutionVersion())
- .arg(solution->versionInfo().version().majorVersion());
+ auto &out = *d->device;
+ out << u8"Microsoft Visual Studio Solution File, Format Version "
+ << solution->versionInfo().solutionVersion().toStdString()
+ << u8"\n# Visual Studio "
+ << solution->versionInfo().version().majorVersion()
+ << u8"\n";
for (const auto &project : solution->fileProjects()) {
- auto projectFilePath = project->filePath();
+ auto projectFilePath = project->filePath().toStdString();
// Try to make the project file path relative to the
// solution file path if we're writing to a file device
- if (!d->baseDir.isEmpty()) {
- const QDir solutionDir(d->baseDir);
+ if (!d->baseDir.empty()) {
+ const QDir solutionDir(QString::fromStdString(d->baseDir));
projectFilePath = Internal::PathUtils::toNativeSeparators(
- solutionDir.relativeFilePath(projectFilePath),
- Internal::HostOsInfo::HostOsWindows);
+ solutionDir.relativeFilePath(QString::fromStdString(projectFilePath)),
+ Internal::HostOsInfo::HostOsWindows).toStdString();
}
- out << QStringLiteral("Project(\"%1\") = \"%2\", \"%3\", \"%4\"\n")
- .arg(project->projectTypeGuid().toString())
- .arg(QFileInfo(projectFilePath).baseName())
- .arg(projectFilePath)
- .arg(project->guid().toString());
+ out << u8"Project(\""
+ << project->projectTypeGuid().toString().toStdString()
+ << u8"\") = \""
+ << QFileInfo(QString::fromStdString(projectFilePath)).baseName().toStdString()
+ << u8"\", \""
+ << projectFilePath
+ << u8"\", \""
+ << project->guid().toString().toStdString()
+ << u8"\"\n";
const auto dependencies = solution->dependencies(project);
if (!dependencies.isEmpty()) {
- out << "\tProjectSection(ProjectDependencies) = postProject\n";
+ out << u8"\tProjectSection(ProjectDependencies) = postProject\n";
for (const auto &dependency : dependencies)
- out << QStringLiteral("\t\t%1 = %1\n").arg(dependency->guid().toString());
+ out << u8"\t\t"
+ << dependency->guid().toString().toStdString()
+ << u8" = "
+ << dependency->guid().toString().toStdString()
+ << u8"\n";
- out << "\tEndProjectSection\n";
+ out << u8"\tEndProjectSection\n";
}
- out << "EndProject\n";
+ out << u8"EndProject\n";
}
for (const auto &project : solution->folderProjects()) {
- out << QStringLiteral("Project(\"%1\") = \"%2\", \"%3\", \"%4\"\n")
- .arg(project->projectTypeGuid().toString())
- .arg(project->name())
- .arg(project->name())
- .arg(project->guid().toString());
- out << QStringLiteral("EndProject\n");
+ out << u8"Project(\""
+ << project->projectTypeGuid().toString().toStdString()
+ << u8"\") = \""
+ << project->name().toStdString()
+ << u8"\", \""
+ << project->name().toStdString()
+ << u8"\", \""
+ << project->guid().toString().toStdString()
+ << u8"\"\n";
+
+ out << u8"EndProject\n";
}
- out << "Global\n";
+ out << u8"Global\n";
for (const auto &globalSection : solution->globalSections()) {
- out << QStringLiteral("\tGlobalSection(%1) = %2\n")
- .arg(globalSection->name())
- .arg(globalSection->isPost()
- ? QStringLiteral("postSolution")
- : QStringLiteral("preSolution"));
+ out << u8"\tGlobalSection("
+ << globalSection->name().toStdString()
+ << u8") = "
+ << (globalSection->isPost() ? u8"postSolution" : u8"preSolution")
+ << u8"\n";
for (const auto &property : globalSection->properties())
- out << QStringLiteral("\t\t%1 = %2\n").arg(property.first).arg(property.second);
- out << "\tEndGlobalSection\n";
+ out << u8"\t\t"
+ << property.first.toStdString()
+ << u8" = "
+ << property.second.toStdString()
+ << u8"\n";
+
+ out << u8"\tEndGlobalSection\n";
}
- out << "EndGlobal\n";
+ out << u8"EndGlobal\n";
- return out.status() == QTextStream::Ok;
+ return out.good();
}
} // namespace qbs
diff --git a/src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.h b/src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.h
index d5b0107f0..62ab45ead 100644
--- a/src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.h
+++ b/src/plugins/generator/visualstudio/io/visualstudiosolutionwriter.h
@@ -31,8 +31,11 @@
#ifndef VISUALSTUDIOSOLUTIONWRITER_H
#define VISUALSTUDIOSOLUTIONWRITER_H
-#include <QtCore/qiodevice.h>
-#include <QtCore/qscopedpointer.h>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include <QtCore/qglobal.h>
namespace qbs {
@@ -45,18 +48,18 @@ class VisualStudioSolutionWriter
{
Q_DISABLE_COPY(VisualStudioSolutionWriter)
public:
- explicit VisualStudioSolutionWriter(QIODevice *device);
+ explicit VisualStudioSolutionWriter(std::ostream *device);
~VisualStudioSolutionWriter();
- QString projectBaseDirectory() const;
- void setProjectBaseDirectory(const QString &dir);
+ std::string projectBaseDirectory() const;
+ void setProjectBaseDirectory(const std::string &dir);
bool write(const VisualStudioSolution *solution);
private:
void addDefaultGlobalSections();
- QScopedPointer<VisualStudioSolutionWriterPrivate> d;
+ std::unique_ptr<VisualStudioSolutionWriterPrivate> d;
};
} // namespace qbs
diff --git a/src/plugins/generator/visualstudio/visualstudio.pro b/src/plugins/generator/visualstudio/visualstudio.pro
index 7f464f1ce..196709bad 100644
--- a/src/plugins/generator/visualstudio/visualstudio.pro
+++ b/src/plugins/generator/visualstudio/visualstudio.pro
@@ -1,4 +1,5 @@
include(../../plugins.pri)
+include(../../../shared/json/json.pri)
TARGET = visualstudiogenerator
diff --git a/src/plugins/generator/visualstudio/visualstudio.qbs b/src/plugins/generator/visualstudio/visualstudio.qbs
index cb22ea5d9..41a620252 100644
--- a/src/plugins/generator/visualstudio/visualstudio.qbs
+++ b/src/plugins/generator/visualstudio/visualstudio.qbs
@@ -2,6 +2,8 @@ import qbs
import "../../qbsplugin.qbs" as QbsPlugin
QbsPlugin {
+ Depends { name: "qbsjson" }
+
name: "visualstudiogenerator"
files: ["visualstudiogeneratorplugin.cpp"]
diff --git a/src/plugins/generator/visualstudio/visualstudiogenerator.cpp b/src/plugins/generator/visualstudio/visualstudiogenerator.cpp
index 5b69ade31..e5cf62003 100644
--- a/src/plugins/generator/visualstudio/visualstudiogenerator.cpp
+++ b/src/plugins/generator/visualstudio/visualstudiogenerator.cpp
@@ -249,7 +249,7 @@ static void writeProjectFiles(const QMap<QString, std::shared_ptr<MSBuildProject
while (it.hasNext()) {
it.next();
const auto projectFilePath = it.key();
- Internal::FileSaver file(projectFilePath);
+ Internal::FileSaver file(projectFilePath.toStdString());
if (!file.open())
throw ErrorInfo(Tr::tr("Cannot open %s for writing").arg(projectFilePath));
@@ -264,12 +264,12 @@ static void writeSolution(const std::shared_ptr<VisualStudioSolution> &solution,
const QString &solutionFilePath,
const Internal::Logger &logger)
{
- Internal::FileSaver file(solutionFilePath);
+ Internal::FileSaver file(solutionFilePath.toStdString());
if (!file.open())
throw ErrorInfo(Tr::tr("Cannot open %s for writing").arg(solutionFilePath));
VisualStudioSolutionWriter writer(file.device());
- writer.setProjectBaseDirectory(QFileInfo(solutionFilePath).path());
+ writer.setProjectBaseDirectory(QFileInfo(solutionFilePath).path().toStdString());
if (!(writer.write(solution.get()) && file.commit()))
throw ErrorInfo(Tr::tr("Failed to generate %1").arg(solutionFilePath));
@@ -300,7 +300,8 @@ void VisualStudioGenerator::visitProject(const GeneratableProject &project)
const auto buildDir = project.baseBuildDirectory();
d->guidPool = std::make_shared<VisualStudioGuidPool>(
- buildDir.absoluteFilePath(project.name() + QStringLiteral(".guid.txt")));
+ buildDir.absoluteFilePath(project.name()
+ + QStringLiteral(".guid.txt")).toStdString());
d->solutionFilePath = buildDir.absoluteFilePath(project.name() + QStringLiteral(".sln"));
d->solution = std::make_shared<VisualStudioSolution>(d->versionInfo);
@@ -311,7 +312,7 @@ void VisualStudioGenerator::visitProject(const GeneratableProject &project)
const auto relativeProjectFilePath = QFileInfo(d->solutionFilePath).dir()
.relativeFilePath(projectFilePath);
auto targetProject = std::make_shared<MSBuildQbsGenerateProject>(project, d->versionInfo);
- targetProject->setGuid(d->guidPool->drawProductGuid(relativeProjectFilePath));
+ targetProject->setGuid(d->guidPool->drawProductGuid(relativeProjectFilePath.toStdString()));
d->msbuildProjects.insert(projectFilePath, targetProject);
addPropertySheets(targetProject);
@@ -346,7 +347,7 @@ void VisualStudioGenerator::visitProduct(const GeneratableProject &project,
.dir().relativeFilePath(projectFilePath);
auto targetProject = std::make_shared<MSBuildQbsProductProject>(project, productData,
d->versionInfo);
- targetProject->setGuid(d->guidPool->drawProductGuid(relativeProjectFilePath));
+ targetProject->setGuid(d->guidPool->drawProductGuid(relativeProjectFilePath.toStdString()));
addPropertySheets(targetProject);
diff --git a/src/plugins/generator/visualstudio/visualstudioguidpool.cpp b/src/plugins/generator/visualstudio/visualstudioguidpool.cpp
index 543d64063..780735fcb 100644
--- a/src/plugins/generator/visualstudio/visualstudioguidpool.cpp
+++ b/src/plugins/generator/visualstudio/visualstudioguidpool.cpp
@@ -30,32 +30,40 @@
#include "visualstudioguidpool.h"
#include <tools/filesaver.h>
-#include <QtCore/qfile.h>
-#include <QtCore/qjsondocument.h>
-#include <QtCore/qmap.h>
-#include <QtCore/qvariant.h>
+#include <QtCore/quuid.h>
+
+#include <fstream>
+#include <map>
+#include <memory>
+
+#include <json.h>
+
+using namespace Json;
namespace qbs {
class VisualStudioGuidPoolPrivate
{
public:
- QString storeFilePath;
- QMap<QString, QUuid> productGuids;
+ std::string storeFilePath;
+ std::map<std::string, QUuid> productGuids;
};
-VisualStudioGuidPool::VisualStudioGuidPool(const QString &storeFilePath)
- : d(new VisualStudioGuidPoolPrivate)
+VisualStudioGuidPool::VisualStudioGuidPool(const std::string &storeFilePath)
+ : d(std::make_shared<VisualStudioGuidPoolPrivate>())
{
// Read any existing GUIDs from the on-disk store
- QFile file(d->storeFilePath = storeFilePath);
- if (file.exists() && file.open(QIODevice::ReadOnly)) {
- const auto data = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
-
- QMapIterator<QString, QVariant> it = data;
- while (it.hasNext()) {
- it.next();
- d->productGuids.insert(it.key(), QUuid(it.value().toString()));
+ std::ifstream file(d->storeFilePath = storeFilePath);
+ if (file.is_open()) {
+ const auto data = JsonDocument::fromJson(std::string {
+ std::istreambuf_iterator<std::ifstream::char_type>(file),
+ std::istreambuf_iterator<std::ifstream::char_type>()
+ }).object();
+ for (auto it = data.constBegin(), end = data.constEnd(); it != end; ++it) {
+ d->productGuids.insert({
+ it.key(),
+ QUuid(QString::fromStdString(it.value().toString()))
+ });
}
}
}
@@ -64,23 +72,21 @@ VisualStudioGuidPool::~VisualStudioGuidPool()
{
Internal::FileSaver file(d->storeFilePath);
if (file.open()) {
- QVariantMap productData;
- QMapIterator<QString, QUuid> it(d->productGuids);
- while (it.hasNext()) {
- it.next();
- productData.insert(it.key(), it.value().toString());
- }
+ JsonObject productData;
+ for (const auto &it : d->productGuids)
+ productData.insert(it.first, it.second.toString().toStdString());
- file.write(QJsonDocument::fromVariant(productData).toJson());
+ const auto data = JsonDocument(productData).toJson();
+ file.write(std::vector<char> { data.cbegin(), data.cend() });
file.commit();
}
}
-QUuid VisualStudioGuidPool::drawProductGuid(const QString &productName)
+QUuid VisualStudioGuidPool::drawProductGuid(const std::string &productName)
{
- if (!d->productGuids.contains(productName))
- d->productGuids.insert(productName, QUuid::createUuid());
- return d->productGuids.value(productName);
+ if (d->productGuids.find(productName) == d->productGuids.cend())
+ d->productGuids.insert({ productName, QUuid::createUuid() });
+ return d->productGuids.at(productName);
}
} // namespace qbs
diff --git a/src/plugins/generator/visualstudio/visualstudioguidpool.h b/src/plugins/generator/visualstudio/visualstudioguidpool.h
index a21bb1f94..dd6dc9eba 100644
--- a/src/plugins/generator/visualstudio/visualstudioguidpool.h
+++ b/src/plugins/generator/visualstudio/visualstudioguidpool.h
@@ -31,8 +31,8 @@
#ifndef VISUALSTUDIOGUIDPOOL_H
#define VISUALSTUDIOGUIDPOOL_H
-#include <QtCore/qobject.h>
-#include <QtCore/qscopedpointer.h>
+#include <memory>
+
#include <QtCore/quuid.h>
namespace qbs {
@@ -48,13 +48,13 @@ class VisualStudioGuidPoolPrivate;
class VisualStudioGuidPool
{
public:
- explicit VisualStudioGuidPool(const QString &storeFilePath);
+ explicit VisualStudioGuidPool(const std::string &storeFilePath);
~VisualStudioGuidPool();
- QUuid drawProductGuid(const QString &productName);
+ QUuid drawProductGuid(const std::string &productName);
private:
- QScopedPointer<VisualStudioGuidPoolPrivate> d;
+ std::shared_ptr<VisualStudioGuidPoolPrivate> d;
};
} // namespace qbs
diff --git a/src/shared/json/README.md b/src/shared/json/README.md
new file mode 100644
index 000000000..fa4a151a8
--- /dev/null
+++ b/src/shared/json/README.md
@@ -0,0 +1,2 @@
+This is QJson without Qt, to be used in circumstances
+where a Qt dependency is not desirable.
diff --git a/src/shared/json/json.cpp b/src/shared/json/json.cpp
new file mode 100644
index 000000000..fbf467924
--- /dev/null
+++ b/src/shared/json/json.cpp
@@ -0,0 +1,4964 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <algorithm>
+#include <atomic>
+#include <cmath>
+#include <iostream>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <limits.h>
+#include <string.h>
+
+#include "json.h"
+
+
+//#define PARSER_DEBUG
+#ifdef PARSER_DEBUG
+static int indent = 0;
+#define BEGIN std::cerr << std::string(4*indent++, ' ').data() << " pos=" << current
+#define END --indent
+#define DEBUG std::cerr << std::string(4*indent, ' ').data()
+#else
+#define BEGIN if (1) ; else std::cerr
+#define END do {} while (0)
+#define DEBUG if (1) ; else std::cerr
+#endif
+
+static const int nestingLimit = 1024;
+
+
+namespace Json {
+namespace Internal {
+
+/*
+ This defines a binary data structure for Json data. The data structure is optimised for fast reading
+ and minimum allocations. The whole data structure can be mmap'ed and used directly.
+
+ In most cases the binary structure is not as space efficient as a utf8 encoded text representation, but
+ much faster to access.
+
+ The size requirements are:
+
+ String: 4 bytes header + 2*(string.length())
+
+ Values: 4 bytes + size of data (size can be 0 for some data)
+ bool: 0 bytes
+ double: 8 bytes (0 if integer with less than 27bits)
+ string: see above
+ array: size of array
+ object: size of object
+ Array: 12 bytes + 4*length + size of Value data
+ Object: 12 bytes + 8*length + size of Key Strings + size of Value data
+
+ For an example such as
+
+ { // object: 12 + 5*8 = 52
+ "firstName": "John", // key 12, value 8 = 20
+ "lastName" : "Smith", // key 12, value 8 = 20
+ "age" : 25, // key 8, value 0 = 8
+ "address" : // key 12, object below = 140
+ { // object: 12 + 4*8
+ "streetAddress": "21 2nd Street", // key 16, value 16
+ "city" : "New York", // key 8, value 12
+ "state" : "NY", // key 8, value 4
+ "postalCode" : "10021" // key 12, value 8
+ }, // object total: 128
+ "phoneNumber": // key: 16, value array below = 172
+ [ // array: 12 + 2*4 + values below: 156
+ { // object 12 + 2*8
+ "type" : "home", // key 8, value 8
+ "number": "212 555-1234" // key 8, value 16
+ }, // object total: 68
+ { // object 12 + 2*8
+ "type" : "fax", // key 8, value 8
+ "number": "646 555-4567" // key 8, value 16
+ } // object total: 68
+ ] // array total: 156
+ } // great total: 412 bytes
+
+ The uncompressed text file used roughly 500 bytes, so in this case we end up using about
+ the same space as the text representation.
+
+ Other measurements have shown a slightly bigger binary size than a compact text
+ representation where all possible whitespace was stripped out.
+*/
+
+class Array;
+class Object;
+class Value;
+class Entry;
+
+template<int pos, int width>
+class qle_bitfield
+{
+public:
+ uint32_t val;
+
+ enum {
+ mask = ((1u << width) - 1) << pos
+ };
+
+ void operator=(uint32_t t) {
+ uint32_t i = val;
+ i &= ~mask;
+ i |= t << pos;
+ val = i;
+ }
+ operator uint32_t() const {
+ uint32_t t = val;
+ t &= mask;
+ t >>= pos;
+ return t;
+ }
+ bool operator!() const { return !operator uint32_t(); }
+ bool operator==(uint32_t t) { return uint32_t(*this) == t; }
+ bool operator!=(uint32_t t) { return uint32_t(*this) != t; }
+ bool operator<(uint32_t t) { return uint32_t(*this) < t; }
+ bool operator>(uint32_t t) { return uint32_t(*this) > t; }
+ bool operator<=(uint32_t t) { return uint32_t(*this) <= t; }
+ bool operator>=(uint32_t t) { return uint32_t(*this) >= t; }
+ void operator+=(uint32_t i) { *this = (uint32_t(*this) + i); }
+ void operator-=(uint32_t i) { *this = (uint32_t(*this) - i); }
+};
+
+template<int pos, int width>
+class qle_signedbitfield
+{
+public:
+ uint32_t val;
+
+ enum {
+ mask = ((1u << width) - 1) << pos
+ };
+
+ void operator=(int t) {
+ uint32_t i = val;
+ i &= ~mask;
+ i |= t << pos;
+ val = i;
+ }
+ operator int() const {
+ uint32_t i = val;
+ i <<= 32 - width - pos;
+ int t = (int) i;
+ t >>= pos;
+ return t;
+ }
+
+ bool operator!() const { return !operator int(); }
+ bool operator==(int t) { return int(*this) == t; }
+ bool operator!=(int t) { return int(*this) != t; }
+ bool operator<(int t) { return int(*this) < t; }
+ bool operator>(int t) { return int(*this) > t; }
+ bool operator<=(int t) { return int(*this) <= t; }
+ bool operator>=(int t) { return int(*this) >= t; }
+ void operator+=(int i) { *this = (int(*this) + i); }
+ void operator-=(int i) { *this = (int(*this) - i); }
+};
+
+typedef uint32_t offset;
+
+// round the size up to the next 4 byte boundary
+int alignedSize(int size) { return (size + 3) & ~3; }
+
+static int qStringSize(const std::string &ba)
+{
+ int l = 4 + static_cast<int>(ba.length());
+ return alignedSize(l);
+}
+
+// returns INT_MAX if it can't compress it into 28 bits
+static int compressedNumber(double d)
+{
+ // this relies on details of how ieee floats are represented
+ const int exponent_off = 52;
+ const uint64_t fraction_mask = 0x000fffffffffffffull;
+ const uint64_t exponent_mask = 0x7ff0000000000000ull;
+
+ uint64_t val;
+ memcpy (&val, &d, sizeof(double));
+ int exp = (int)((val & exponent_mask) >> exponent_off) - 1023;
+ if (exp < 0 || exp > 25)
+ return INT_MAX;
+
+ uint64_t non_int = val & (fraction_mask >> exp);
+ if (non_int)
+ return INT_MAX;
+
+ bool neg = (val >> 63) != 0;
+ val &= fraction_mask;
+ val |= ((uint64_t)1 << 52);
+ int res = (int)(val >> (52 - exp));
+ return neg ? -res : res;
+}
+
+static void toInternal(char *addr, const char *data, int size)
+{
+ memcpy(addr, &size, 4);
+ memcpy(addr + 4, data, size);
+}
+
+class String
+{
+public:
+ String(const char *data) { d = (Data *)data; }
+
+ struct Data {
+ int length;
+ char utf8[1];
+ };
+
+ Data *d;
+
+ void operator=(const std::string &ba)
+ {
+ d->length = static_cast<int>(ba.length());
+ memcpy(d->utf8, ba.data(), ba.length());
+ }
+
+ bool operator==(const std::string &ba) const {
+ return toString() == ba;
+ }
+ bool operator!=(const std::string &str) const {
+ return !operator==(str);
+ }
+ bool operator>=(const std::string &str) const {
+ // ###
+ return toString() >= str;
+ }
+
+ bool operator==(const String &str) const {
+ if (d->length != str.d->length)
+ return false;
+ return !memcmp(d->utf8, str.d->utf8, d->length);
+ }
+ bool operator<(const String &other) const;
+ bool operator>=(const String &other) const { return !(*this < other); }
+
+ std::string toString() const {
+ return std::string(d->utf8, d->length);
+ }
+
+};
+
+bool String::operator<(const String &other) const
+{
+ int alen = d->length;
+ int blen = other.d->length;
+ int l = std::min(alen, blen);
+ char *a = d->utf8;
+ char *b = other.d->utf8;
+
+ while (l-- && *a == *b)
+ a++,b++;
+ if (l==-1)
+ return (alen < blen);
+ return (unsigned char)(*a) < (unsigned char)(*b);
+}
+
+static void copyString(char *dest, const std::string &str)
+{
+ String string(dest);
+ string = str;
+}
+
+
+/*
+ Base is the base class for both Object and Array. Both classe work more or less the same way.
+ The class starts with a header (defined by the struct below), then followed by data (the data for
+ values in the Array case and Entry's (see below) for objects.
+
+ After the data a table follows (tableOffset points to it) containing Value objects for Arrays, and
+ offsets from the beginning of the object to Entry's in the case of Object.
+
+ Entry's in the Object's table are lexicographically sorted by key in the table(). This allows the usage
+ of a binary search over the keys in an Object.
+ */
+class Base
+{
+public:
+ uint32_t size;
+ union {
+ uint32_t _dummy;
+ qle_bitfield<0, 1> is_object;
+ qle_bitfield<1, 31> length;
+ };
+ offset tableOffset;
+ // content follows here
+
+ bool isObject() const { return !!is_object; }
+ bool isArray() const { return !isObject(); }
+
+ offset *table() const { return (offset *) (((char *) this) + tableOffset); }
+
+ int reserveSpace(uint32_t dataSize, int posInTable, uint32_t numItems, bool replace);
+ void removeItems(int pos, int numItems);
+};
+
+class Object : public Base
+{
+public:
+ Entry *entryAt(int i) const {
+ return reinterpret_cast<Entry *>(((char *)this) + table()[i]);
+ }
+ int indexOf(const std::string &key, bool *exists);
+
+ bool isValid() const;
+};
+
+
+class Value
+{
+public:
+ enum {
+ MaxSize = (1<<27) - 1
+ };
+ union {
+ uint32_t _dummy;
+ qle_bitfield<0, 3> type;
+ qle_bitfield<3, 1> intValue;
+ qle_bitfield<4, 1> _; // Ex-latin1Key
+ qle_bitfield<5, 27> value; // Used as offset in case of Entry(?)
+ qle_signedbitfield<5, 27> int_value;
+ };
+
+ char *data(const Base *b) const { return ((char *)b) + value; }
+ int usedStorage(const Base *b) const;
+
+ bool toBoolean() const { return value != 0; }
+ double toDouble(const Base *b) const;
+ std::string toString(const Base *b) const;
+ Base *base(const Base *b) const;
+
+ bool isValid(const Base *b) const;
+
+ static int requiredStorage(JsonValue &v, bool *compressed);
+ static uint32_t valueToStore(const JsonValue &v, uint32_t offset);
+ static void copyData(const JsonValue &v, char *dest, bool compressed);
+};
+
+class Array : public Base
+{
+public:
+ Value at(int i) const { return *(Value *) (table() + i); }
+ Value &operator[](int i) { return *(Value *) (table() + i); }
+
+ bool isValid() const;
+};
+
+class Entry {
+public:
+ Value value;
+ // key
+ // value data follows key
+
+ int size() const
+ {
+ int s = sizeof(Entry);
+ s += sizeof(uint32_t) + (*(int *) ((const char *)this + sizeof(Entry)));
+ return alignedSize(s);
+ }
+
+ int usedStorage(Base *b) const
+ {
+ return size() + value.usedStorage(b);
+ }
+
+ String shallowKey() const
+ {
+ return String((const char *)this + sizeof(Entry));
+ }
+
+ std::string key() const
+ {
+ return shallowKey().toString();
+ }
+
+ bool operator==(const std::string &key) const;
+ bool operator!=(const std::string &key) const { return !operator==(key); }
+ bool operator>=(const std::string &key) const { return shallowKey() >= key; }
+
+ bool operator==(const Entry &other) const;
+ bool operator>=(const Entry &other) const;
+};
+
+bool operator<(const std::string &key, const Entry &e)
+{
+ return e >= key;
+}
+
+
+class Header
+{
+public:
+ uint32_t tag; // 'qbjs'
+ uint32_t version; // 1
+ Base *root() { return (Base *)(this + 1); }
+};
+
+
+double Value::toDouble(const Base *b) const
+{
+ // assert(type == JsonValue::Double);
+ if (intValue)
+ return int_value;
+
+ double d;
+ memcpy(&d, (const char *)b + value, 8);
+ return d;
+}
+
+std::string Value::toString(const Base *b) const
+{
+ String s(data(b));
+ return s.toString();
+}
+
+Base *Value::base(const Base *b) const
+{
+ // assert(type == JsonValue::Array || type == JsonValue::Object);
+ return reinterpret_cast<Base *>(data(b));
+}
+
+class AtomicInt
+{
+public:
+ bool ref() { return ++x != 0; }
+ bool deref() { return --x != 0; }
+ int load() { return x.load(std::memory_order_seq_cst); }
+private:
+ std::atomic<int> x { 0 };
+};
+
+
+class SharedString
+{
+public:
+ AtomicInt ref;
+ std::string s;
+};
+
+class Data {
+public:
+ enum Validation {
+ Unchecked,
+ Validated,
+ Invalid
+ };
+
+ AtomicInt ref;
+ int alloc;
+ union {
+ char *rawData;
+ Header *header;
+ };
+ uint32_t compactionCounter : 31;
+ uint32_t ownsData : 1;
+
+ Data(char *raw, int a)
+ : alloc(a), rawData(raw), compactionCounter(0), ownsData(true)
+ {
+ }
+ Data(int reserved, JsonValue::Type valueType)
+ : rawData(0), compactionCounter(0), ownsData(true)
+ {
+ // assert(valueType == JsonValue::Array || valueType == JsonValue::Object);
+
+ alloc = sizeof(Header) + sizeof(Base) + reserved + sizeof(offset);
+ header = (Header *)malloc(alloc);
+ header->tag = JsonDocument::BinaryFormatTag;
+ header->version = 1;
+ Base *b = header->root();
+ b->size = sizeof(Base);
+ b->is_object = (valueType == JsonValue::Object);
+ b->tableOffset = sizeof(Base);
+ b->length = 0;
+ }
+ ~Data()
+ { if (ownsData) free(rawData); }
+
+ uint32_t offsetOf(const void *ptr) const { return (uint32_t)(((char *)ptr - rawData)); }
+
+ JsonObject toObject(Object *o) const
+ {
+ return JsonObject(const_cast<Data *>(this), o);
+ }
+
+ JsonArray toArray(Array *a) const
+ {
+ return JsonArray(const_cast<Data *>(this), a);
+ }
+
+ Data *clone(Base *b, int reserve = 0)
+ {
+ int size = sizeof(Header) + b->size;
+ if (b == header->root() && ref.load() == 1 && alloc >= size + reserve)
+ return this;
+
+ if (reserve) {
+ if (reserve < 128)
+ reserve = 128;
+ size = std::max(size + reserve, size *2);
+ }
+ char *raw = (char *)malloc(size);
+ memcpy(raw + sizeof(Header), b, b->size);
+ Header *h = (Header *)raw;
+ h->tag = JsonDocument::BinaryFormatTag;
+ h->version = 1;
+ Data *d = new Data(raw, size);
+ d->compactionCounter = (b == header->root()) ? compactionCounter : 0;
+ return d;
+ }
+
+ void compact();
+ bool valid() const;
+
+private:
+ Data(const Data &);
+ void operator=(const Data &);
+};
+
+
+void objectToJson(const Object *o, std::string &json, int indent, bool compact = false);
+void arrayToJson(const Array *a, std::string &json, int indent, bool compact = false);
+
+class Parser
+{
+public:
+ Parser(const char *json, int length);
+
+ JsonDocument parse(JsonParseError *error);
+
+ class ParsedObject
+ {
+ public:
+ ParsedObject(Parser *p, int pos) : parser(p), objectPosition(pos) {
+ offsets.reserve(64);
+ }
+ void insert(uint32_t offset);
+
+ Parser *parser;
+ int objectPosition;
+ std::vector<uint32_t> offsets;
+
+ Entry *entryAt(size_t i) const {
+ return reinterpret_cast<Entry *>(parser->data + objectPosition + offsets[i]);
+ }
+ };
+
+
+private:
+ void eatBOM();
+ bool eatSpace();
+ char nextToken();
+
+ bool parseObject();
+ bool parseArray();
+ bool parseMember(int baseOffset);
+ bool parseString();
+ bool parseEscapeSequence();
+ bool parseValue(Value *val, int baseOffset);
+ bool parseNumber(Value *val, int baseOffset);
+
+ void addChar(char c) {
+ const int pos = reserveSpace(1);
+ data[pos] = c;
+ }
+
+ const char *head;
+ const char *json;
+ const char *end;
+
+ char *data;
+ int dataLength;
+ int current;
+ int nestingLevel;
+ JsonParseError::ParseError lastError;
+
+ int reserveSpace(int space) {
+ if (current + space >= dataLength) {
+ dataLength = 2*dataLength + space;
+ data = (char *)realloc(data, dataLength);
+ }
+ int pos = current;
+ current += space;
+ return pos;
+ }
+};
+
+} // namespace Internal
+
+using namespace Internal;
+
+/*!
+ \class JsonValue
+ \inmodule QtCore
+ \ingroup json
+ \ingroup shared
+ \reentrant
+ \since 5.0
+
+ \brief The JsonValue class encapsulates a value in JSON.
+
+ A value in JSON can be one of 6 basic types:
+
+ JSON is a format to store structured data. It has 6 basic data types:
+
+ \list
+ \li bool JsonValue::Bool
+ \li double JsonValue::Double
+ \li string JsonValue::String
+ \li array JsonValue::Array
+ \li object JsonValue::Object
+ \li null JsonValue::Null
+ \endlist
+
+ A value can represent any of the above data types. In addition, JsonValue has one special
+ flag to represent undefined values. This can be queried with isUndefined().
+
+ The type of the value can be queried with type() or accessors like isBool(), isString(), and so on.
+ Likewise, the value can be converted to the type stored in it using the toBool(), toString() and so on.
+
+ Values are strictly typed internally and contrary to QVariant will not attempt to do any implicit type
+ conversions. This implies that converting to a type that is not stored in the value will return a default
+ constructed return value.
+
+ \section1 JsonValueRef
+
+ JsonValueRef is a helper class for JsonArray and JsonObject.
+ When you get an object of type JsonValueRef, you can
+ use it as if it were a reference to a JsonValue. If you assign to it,
+ the assignment will apply to the element in the JsonArray or JsonObject
+ from which you got the reference.
+
+ The following methods return JsonValueRef:
+ \list
+ \li \l {JsonArray}::operator[](int i)
+ \li \l {JsonObject}::operator[](const QString & key) const
+ \endlist
+
+ \sa {JSON Support in Qt}, {JSON Save Game Example}
+*/
+
+/*!
+ Creates a JsonValue of type \a type.
+
+ The default is to create a Null value.
+ */
+JsonValue::JsonValue(Type type)
+ : ui(0), d(0), t(type)
+{
+}
+
+/*!
+ \internal
+ */
+JsonValue::JsonValue(Internal::Data *data, Internal::Base *base, const Internal::Value &v)
+ : d(0), t((Type)(uint32_t)v.type)
+{
+ switch (t) {
+ case Undefined:
+ case Null:
+ dbl = 0;
+ break;
+ case Bool:
+ b = v.toBoolean();
+ break;
+ case Double:
+ dbl = v.toDouble(base);
+ break;
+ case String: {
+ stringData = new Internal::SharedString;
+ stringData->s = v.toString(base);
+ stringData->ref.ref();
+ break;
+ }
+ case Array:
+ case Object:
+ d = data;
+ this->base = v.base(base);
+ break;
+ }
+ if (d)
+ d->ref.ref();
+}
+
+/*!
+ Creates a value of type Bool, with value \a b.
+ */
+JsonValue::JsonValue(bool b)
+ : d(0), t(Bool)
+{
+ this->b = b;
+}
+
+/*!
+ Creates a value of type Double, with value \a n.
+ */
+JsonValue::JsonValue(double n)
+ : d(0), t(Double)
+{
+ this->dbl = n;
+}
+
+/*!
+ \overload
+ Creates a value of type Double, with value \a n.
+ */
+JsonValue::JsonValue(int n)
+ : d(0), t(Double)
+{
+ this->dbl = n;
+}
+
+/*!
+ \overload
+ Creates a value of type Double, with value \a n.
+ NOTE: the integer limits for IEEE 754 double precision data is 2^53 (-9007199254740992 to +9007199254740992).
+ If you pass in values outside this range expect a loss of precision to occur.
+ */
+JsonValue::JsonValue(int64_t n)
+ : d(0), t(Double)
+{
+ this->dbl = double(n);
+}
+
+/*!
+ Creates a value of type String, with value \a s.
+ */
+JsonValue::JsonValue(const std::string &s)
+ : d(0), t(String)
+{
+ stringData = new Internal::SharedString;
+ stringData->s = s;
+ stringData->ref.ref();
+}
+
+JsonValue::JsonValue(const char *s)
+ : d(0), t(String)
+{
+ stringData = new Internal::SharedString;
+ stringData->s = s;
+ stringData->ref.ref();
+}
+
+/*!
+ Creates a value of type Array, with value \a a.
+ */
+JsonValue::JsonValue(const JsonArray &a)
+ : d(a.d), t(Array)
+{
+ base = a.a;
+ if (d)
+ d->ref.ref();
+}
+
+/*!
+ Creates a value of type Object, with value \a o.
+ */
+JsonValue::JsonValue(const JsonObject &o)
+ : d(o.d), t(Object)
+{
+ base = o.o;
+ if (d)
+ d->ref.ref();
+}
+
+
+/*!
+ Destroys the value.
+ */
+JsonValue::~JsonValue()
+{
+ if (t == String && stringData && !stringData->ref.deref())
+ free(stringData);
+
+ if (d && !d->ref.deref())
+ delete d;
+}
+
+/*!
+ Creates a copy of \a other.
+ */
+JsonValue::JsonValue(const JsonValue &other)
+ : t(other.t)
+{
+ d = other.d;
+ ui = other.ui;
+ if (d)
+ d->ref.ref();
+
+ if (t == String && stringData)
+ stringData->ref.ref();
+}
+
+/*!
+ Assigns the value stored in \a other to this object.
+ */
+JsonValue &JsonValue::operator=(const JsonValue &other)
+{
+ if (t == String && stringData && !stringData->ref.deref())
+ free(stringData);
+
+ t = other.t;
+ dbl = other.dbl;
+
+ if (d != other.d) {
+
+ if (d && !d->ref.deref())
+ delete d;
+ d = other.d;
+ if (d)
+ d->ref.ref();
+
+ }
+
+ if (t == String && stringData)
+ stringData->ref.ref();
+
+ return *this;
+}
+
+/*!
+ \fn bool JsonValue::isNull() const
+
+ Returns \c true if the value is null.
+*/
+
+/*!
+ \fn bool JsonValue::isBool() const
+
+ Returns \c true if the value contains a boolean.
+
+ \sa toBool()
+ */
+
+/*!
+ \fn bool JsonValue::isDouble() const
+
+ Returns \c true if the value contains a double.
+
+ \sa toDouble()
+ */
+
+/*!
+ \fn bool JsonValue::isString() const
+
+ Returns \c true if the value contains a string.
+
+ \sa toString()
+ */
+
+/*!
+ \fn bool JsonValue::isArray() const
+
+ Returns \c true if the value contains an array.
+
+ \sa toArray()
+ */
+
+/*!
+ \fn bool JsonValue::isObject() const
+
+ Returns \c true if the value contains an object.
+
+ \sa toObject()
+ */
+
+/*!
+ \fn bool JsonValue::isUndefined() const
+
+ Returns \c true if the value is undefined. This can happen in certain
+ error cases as e.g. accessing a non existing key in a JsonObject.
+ */
+
+
+/*!
+ \enum JsonValue::Type
+
+ This enum describes the type of the JSON value.
+
+ \value Null A Null value
+ \value Bool A boolean value. Use toBool() to convert to a bool.
+ \value Double A double. Use toDouble() to convert to a double.
+ \value String A string. Use toString() to convert to a QString.
+ \value Array An array. Use toArray() to convert to a JsonArray.
+ \value Object An object. Use toObject() to convert to a JsonObject.
+ \value Undefined The value is undefined. This is usually returned as an
+ error condition, when trying to read an out of bounds value
+ in an array or a non existent key in an object.
+*/
+
+/*!
+ Returns the type of the value.
+
+ \sa JsonValue::Type
+ */
+
+
+/*!
+ Converts the value to a bool and returns it.
+
+ If type() is not bool, the \a defaultValue will be returned.
+ */
+bool JsonValue::toBool(bool defaultValue) const
+{
+ if (t != Bool)
+ return defaultValue;
+ return b;
+}
+
+/*!
+ Converts the value to an int and returns it.
+
+ If type() is not Double or the value is not a whole number,
+ the \a defaultValue will be returned.
+ */
+int JsonValue::toInt(int defaultValue) const
+{
+ if (t == Double && int(dbl) == dbl)
+ return int(dbl);
+ return defaultValue;
+}
+
+/*!
+ Converts the value to a double and returns it.
+
+ If type() is not Double, the \a defaultValue will be returned.
+ */
+double JsonValue::toDouble(double defaultValue) const
+{
+ if (t != Double)
+ return defaultValue;
+ return dbl;
+}
+
+/*!
+ Converts the value to a QString and returns it.
+
+ If type() is not String, the \a defaultValue will be returned.
+ */
+std::string JsonValue::toString(const std::string &defaultValue) const
+{
+ if (t != String)
+ return defaultValue;
+ return stringData->s;
+}
+
+/*!
+ Converts the value to an array and returns it.
+
+ If type() is not Array, the \a defaultValue will be returned.
+ */
+JsonArray JsonValue::toArray(const JsonArray &defaultValue) const
+{
+ if (!d || t != Array)
+ return defaultValue;
+
+ return JsonArray(d, static_cast<Internal::Array *>(base));
+}
+
+/*!
+ \overload
+
+ Converts the value to an array and returns it.
+
+ If type() is not Array, a \l{JsonArray::}{JsonArray()} will be returned.
+ */
+JsonArray JsonValue::toArray() const
+{
+ return toArray(JsonArray());
+}
+
+/*!
+ Converts the value to an object and returns it.
+
+ If type() is not Object, the \a defaultValue will be returned.
+ */
+JsonObject JsonValue::toObject(const JsonObject &defaultValue) const
+{
+ if (!d || t != Object)
+ return defaultValue;
+
+ return JsonObject(d, static_cast<Internal::Object *>(base));
+}
+
+/*!
+ \overload
+
+ Converts the value to an object and returns it.
+
+ If type() is not Object, the \l {JsonObject::}{JsonObject()} will be returned.
+*/
+JsonObject JsonValue::toObject() const
+{
+ return toObject(JsonObject());
+}
+
+/*!
+ Returns \c true if the value is equal to \a other.
+ */
+bool JsonValue::operator==(const JsonValue &other) const
+{
+ if (t != other.t)
+ return false;
+
+ switch (t) {
+ case Undefined:
+ case Null:
+ break;
+ case Bool:
+ return b == other.b;
+ case Double:
+ return dbl == other.dbl;
+ case String:
+ return toString() == other.toString();
+ case Array:
+ if (base == other.base)
+ return true;
+ if (!base)
+ return !other.base->length;
+ if (!other.base)
+ return !base->length;
+ return JsonArray(d, static_cast<Internal::Array *>(base))
+ == JsonArray(other.d, static_cast<Internal::Array *>(other.base));
+ case Object:
+ if (base == other.base)
+ return true;
+ if (!base)
+ return !other.base->length;
+ if (!other.base)
+ return !base->length;
+ return JsonObject(d, static_cast<Internal::Object *>(base))
+ == JsonObject(other.d, static_cast<Internal::Object *>(other.base));
+ }
+ return true;
+}
+
+/*!
+ Returns \c true if the value is not equal to \a other.
+ */
+bool JsonValue::operator!=(const JsonValue &other) const
+{
+ return !(*this == other);
+}
+
+/*!
+ \internal
+ */
+void JsonValue::detach()
+{
+ if (!d)
+ return;
+
+ Internal::Data *x = d->clone(base);
+ x->ref.ref();
+ if (!d->ref.deref())
+ delete d;
+ d = x;
+ base = static_cast<Internal::Object *>(d->header->root());
+}
+
+
+/*!
+ \class JsonValueRef
+ \inmodule QtCore
+ \reentrant
+ \brief The JsonValueRef class is a helper class for JsonValue.
+
+ \internal
+
+ \ingroup json
+
+ When you get an object of type JsonValueRef, if you can assign to it,
+ the assignment will apply to the character in the string from
+ which you got the reference. That is its whole purpose in life.
+
+ You can use it exactly in the same way as a reference to a JsonValue.
+
+ The JsonValueRef becomes invalid once modifications are made to the
+ string: if you want to keep the character, copy it into a JsonValue.
+
+ Most of the JsonValue member functions also exist in JsonValueRef.
+ However, they are not explicitly documented here.
+*/
+
+
+JsonValueRef &JsonValueRef::operator=(const JsonValue &val)
+{
+ if (is_object)
+ o->setValueAt(index, val);
+ else
+ a->replace(index, val);
+
+ return *this;
+}
+
+JsonValueRef &JsonValueRef::operator=(const JsonValueRef &ref)
+{
+ if (is_object)
+ o->setValueAt(index, ref);
+ else
+ a->replace(index, ref);
+
+ return *this;
+}
+
+JsonArray JsonValueRef::toArray() const
+{
+ return toValue().toArray();
+}
+
+JsonObject JsonValueRef::toObject() const
+{
+ return toValue().toObject();
+}
+
+JsonValue JsonValueRef::toValue() const
+{
+ if (!is_object)
+ return a->at(index);
+ return o->valueAt(index);
+}
+
+/*!
+ \class JsonArray
+ \inmodule QtCore
+ \ingroup json
+ \ingroup shared
+ \reentrant
+ \since 5.0
+
+ \brief The JsonArray class encapsulates a JSON array.
+
+ A JSON array is a list of values. The list can be manipulated by inserting and
+ removing JsonValue's from the array.
+
+ A JsonArray can be converted to and from a QVariantList. You can query the
+ number of entries with size(), insert(), and removeAt() entries from it
+ and iterate over its content using the standard C++ iterator pattern.
+
+ JsonArray is an implicitly shared class and shares the data with the document
+ it has been created from as long as it is not being modified.
+
+ You can convert the array to and from text based JSON through JsonDocument.
+
+ \sa {JSON Support in Qt}, {JSON Save Game Example}
+*/
+
+/*!
+ \typedef JsonArray::Iterator
+
+ Qt-style synonym for JsonArray::iterator.
+*/
+
+/*!
+ \typedef JsonArray::ConstIterator
+
+ Qt-style synonym for JsonArray::const_iterator.
+*/
+
+/*!
+ \typedef JsonArray::size_type
+
+ Typedef for int. Provided for STL compatibility.
+*/
+
+/*!
+ \typedef JsonArray::value_type
+
+ Typedef for JsonValue. Provided for STL compatibility.
+*/
+
+/*!
+ \typedef JsonArray::difference_type
+
+ Typedef for int. Provided for STL compatibility.
+*/
+
+/*!
+ \typedef JsonArray::pointer
+
+ Typedef for JsonValue *. Provided for STL compatibility.
+*/
+
+/*!
+ \typedef JsonArray::const_pointer
+
+ Typedef for const JsonValue *. Provided for STL compatibility.
+*/
+
+/*!
+ \typedef JsonArray::reference
+
+ Typedef for JsonValue &. Provided for STL compatibility.
+*/
+
+/*!
+ \typedef JsonArray::const_reference
+
+ Typedef for const JsonValue &. Provided for STL compatibility.
+*/
+
+/*!
+ Creates an empty array.
+ */
+JsonArray::JsonArray()
+ : d(0), a(0)
+{
+}
+
+JsonArray::JsonArray(std::initializer_list<JsonValue> args)
+ : d(0), a(0)
+{
+ for (auto i = args.begin(); i != args.end(); ++i)
+ append(*i);
+}
+
+/*!
+ \fn JsonArray::JsonArray(std::initializer_list<JsonValue> args)
+ \since 5.4
+ Creates an array initialized from \a args initialization list.
+
+ JsonArray can be constructed in a way similar to JSON notation,
+ for example:
+ \code
+ JsonArray array = { 1, 2.2, QString() };
+ \endcode
+ */
+
+/*!
+ \internal
+ */
+JsonArray::JsonArray(Internal::Data *data, Internal::Array *array)
+ : d(data), a(array)
+{
+ // assert(data);
+ // assert(array);
+ d->ref.ref();
+}
+
+/*!
+ Deletes the array.
+ */
+JsonArray::~JsonArray()
+{
+ if (d && !d->ref.deref())
+ delete d;
+}
+
+/*!
+ Creates a copy of \a other.
+
+ Since JsonArray is implicitly shared, the copy is shallow
+ as long as the object doesn't get modified.
+ */
+JsonArray::JsonArray(const JsonArray &other)
+{
+ d = other.d;
+ a = other.a;
+ if (d)
+ d->ref.ref();
+}
+
+/*!
+ Assigns \a other to this array.
+ */
+JsonArray &JsonArray::operator=(const JsonArray &other)
+{
+ if (d != other.d) {
+ if (d && !d->ref.deref())
+ delete d;
+ d = other.d;
+ if (d)
+ d->ref.ref();
+ }
+ a = other.a;
+
+ return *this;
+}
+
+/*! \fn JsonArray &JsonArray::operator+=(const JsonValue &value)
+
+ Appends \a value to the array, and returns a reference to the array itself.
+
+ \since 5.3
+ \sa append(), operator<<()
+*/
+
+/*! \fn JsonArray JsonArray::operator+(const JsonValue &value) const
+
+ Returns an array that contains all the items in this array followed
+ by the provided \a value.
+
+ \since 5.3
+ \sa operator+=()
+*/
+
+/*! \fn JsonArray &JsonArray::operator<<(const JsonValue &value)
+
+ Appends \a value to the array, and returns a reference to the array itself.
+
+ \since 5.3
+ \sa operator+=(), append()
+*/
+
+/*!
+ Returns the number of values stored in the array.
+ */
+int JsonArray::size() const
+{
+ if (!d)
+ return 0;
+
+ return (int)a->length;
+}
+
+/*!
+ \fn JsonArray::count() const
+
+ Same as size().
+
+ \sa size()
+*/
+
+/*!
+ Returns \c true if the object is empty. This is the same as size() == 0.
+
+ \sa size()
+ */
+bool JsonArray::isEmpty() const
+{
+ if (!d)
+ return true;
+
+ return !a->length;
+}
+
+/*!
+ Returns a JsonValue representing the value for index \a i.
+
+ The returned JsonValue is \c Undefined, if \a i is out of bounds.
+
+ */
+JsonValue JsonArray::at(int i) const
+{
+ if (!a || i < 0 || i >= (int)a->length)
+ return JsonValue(JsonValue::Undefined);
+
+ return JsonValue(d, a, a->at(i));
+}
+
+/*!
+ Returns the first value stored in the array.
+
+ Same as \c at(0).
+
+ \sa at()
+ */
+JsonValue JsonArray::first() const
+{
+ return at(0);
+}
+
+/*!
+ Returns the last value stored in the array.
+
+ Same as \c{at(size() - 1)}.
+
+ \sa at()
+ */
+JsonValue JsonArray::last() const
+{
+ return at(a ? (a->length - 1) : 0);
+}
+
+/*!
+ Inserts \a value at the beginning of the array.
+
+ This is the same as \c{insert(0, value)} and will prepend \a value to the array.
+
+ \sa append(), insert()
+ */
+void JsonArray::prepend(const JsonValue &value)
+{
+ insert(0, value);
+}
+
+/*!
+ Inserts \a value at the end of the array.
+
+ \sa prepend(), insert()
+ */
+void JsonArray::append(const JsonValue &value)
+{
+ insert(a ? (int)a->length : 0, value);
+}
+
+/*!
+ Removes the value at index position \a i. \a i must be a valid
+ index position in the array (i.e., \c{0 <= i < size()}).
+
+ \sa insert(), replace()
+ */
+void JsonArray::removeAt(int i)
+{
+ if (!a || i < 0 || i >= (int)a->length)
+ return;
+
+ detach();
+ a->removeItems(i, 1);
+ ++d->compactionCounter;
+ if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u)
+ compact();
+}
+
+/*! \fn void JsonArray::removeFirst()
+
+ Removes the first item in the array. Calling this function is
+ equivalent to calling \c{removeAt(0)}. The array must not be empty. If
+ the array can be empty, call isEmpty() before calling this
+ function.
+
+ \sa removeAt(), removeLast()
+*/
+
+/*! \fn void JsonArray::removeLast()
+
+ Removes the last item in the array. Calling this function is
+ equivalent to calling \c{removeAt(size() - 1)}. The array must not be
+ empty. If the array can be empty, call isEmpty() before calling
+ this function.
+
+ \sa removeAt(), removeFirst()
+*/
+
+/*!
+ Removes the item at index position \a i and returns it. \a i must
+ be a valid index position in the array (i.e., \c{0 <= i < size()}).
+
+ If you don't use the return value, removeAt() is more efficient.
+
+ \sa removeAt()
+ */
+JsonValue JsonArray::takeAt(int i)
+{
+ if (!a || i < 0 || i >= (int)a->length)
+ return JsonValue(JsonValue::Undefined);
+
+ JsonValue v(d, a, a->at(i));
+ removeAt(i); // detaches
+ return v;
+}
+
+/*!
+ Inserts \a value at index position \a i in the array. If \a i
+ is \c 0, the value is prepended to the array. If \a i is size(), the
+ value is appended to the array.
+
+ \sa append(), prepend(), replace(), removeAt()
+ */
+void JsonArray::insert(int i, const JsonValue &value)
+{
+ // assert (i >= 0 && i <= (a ? (int)a->length : 0));
+ JsonValue val = value;
+
+ bool compressed;
+ int valueSize = Internal::Value::requiredStorage(val, &compressed);
+
+ detach(valueSize + sizeof(Internal::Value));
+
+ if (!a->length)
+ a->tableOffset = sizeof(Internal::Array);
+
+ int valueOffset = a->reserveSpace(valueSize, i, 1, false);
+ if (!valueOffset)
+ return;
+
+ Internal::Value &v = (*a)[i];
+ v.type = (val.t == JsonValue::Undefined ? JsonValue::Null : val.t);
+ v.intValue = compressed;
+ v.value = Internal::Value::valueToStore(val, valueOffset);
+ if (valueSize)
+ Internal::Value::copyData(val, (char *)a + valueOffset, compressed);
+}
+
+/*!
+ \fn JsonArray::iterator JsonArray::insert(iterator before, const JsonValue &value)
+
+ Inserts \a value before the position pointed to by \a before, and returns an iterator
+ pointing to the newly inserted item.
+
+ \sa erase(), insert()
+*/
+
+/*!
+ \fn JsonArray::iterator JsonArray::erase(iterator it)
+
+ Removes the item pointed to by \a it, and returns an iterator pointing to the
+ next item.
+
+ \sa removeAt()
+*/
+
+/*!
+ Replaces the item at index position \a i with \a value. \a i must
+ be a valid index position in the array (i.e., \c{0 <= i < size()}).
+
+ \sa operator[](), removeAt()
+ */
+void JsonArray::replace(int i, const JsonValue &value)
+{
+ // assert (a && i >= 0 && i < (int)(a->length));
+ JsonValue val = value;
+
+ bool compressed;
+ int valueSize = Internal::Value::requiredStorage(val, &compressed);
+
+ detach(valueSize);
+
+ if (!a->length)
+ a->tableOffset = sizeof(Internal::Array);
+
+ int valueOffset = a->reserveSpace(valueSize, i, 1, true);
+ if (!valueOffset)
+ return;
+
+ Internal::Value &v = (*a)[i];
+ v.type = (val.t == JsonValue::Undefined ? JsonValue::Null : val.t);
+ v.intValue = compressed;
+ v.value = Internal::Value::valueToStore(val, valueOffset);
+ if (valueSize)
+ Internal::Value::copyData(val, (char *)a + valueOffset, compressed);
+
+ ++d->compactionCounter;
+ if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u)
+ compact();
+}
+
+/*!
+ Returns \c true if the array contains an occurrence of \a value, otherwise \c false.
+
+ \sa count()
+ */
+bool JsonArray::contains(const JsonValue &value) const
+{
+ for (int i = 0; i < size(); i++) {
+ if (at(i) == value)
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Returns the value at index position \a i as a modifiable reference.
+ \a i must be a valid index position in the array (i.e., \c{0 <= i <
+ size()}).
+
+ The return value is of type JsonValueRef, a helper class for JsonArray
+ and JsonObject. When you get an object of type JsonValueRef, you can
+ use it as if it were a reference to a JsonValue. If you assign to it,
+ the assignment will apply to the character in the JsonArray of JsonObject
+ from which you got the reference.
+
+ \sa at()
+ */
+JsonValueRef JsonArray::operator[](int i)
+{
+ // assert(a && i >= 0 && i < (int)a->length);
+ return JsonValueRef(this, i);
+}
+
+/*!
+ \overload
+
+ Same as at().
+ */
+JsonValue JsonArray::operator[](int i) const
+{
+ return at(i);
+}
+
+/*!
+ Returns \c true if this array is equal to \a other.
+ */
+bool JsonArray::operator==(const JsonArray &other) const
+{
+ if (a == other.a)
+ return true;
+
+ if (!a)
+ return !other.a->length;
+ if (!other.a)
+ return !a->length;
+ if (a->length != other.a->length)
+ return false;
+
+ for (int i = 0; i < (int)a->length; ++i) {
+ if (JsonValue(d, a, a->at(i)) != JsonValue(other.d, other.a, other.a->at(i)))
+ return false;
+ }
+ return true;
+}
+
+/*!
+ Returns \c true if this array is not equal to \a other.
+ */
+bool JsonArray::operator!=(const JsonArray &other) const
+{
+ return !(*this == other);
+}
+
+/*! \fn JsonArray::iterator JsonArray::begin()
+
+ Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in
+ the array.
+
+ \sa constBegin(), end()
+*/
+
+/*! \fn JsonArray::const_iterator JsonArray::begin() const
+
+ \overload
+*/
+
+/*! \fn JsonArray::const_iterator JsonArray::constBegin() const
+
+ Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item
+ in the array.
+
+ \sa begin(), constEnd()
+*/
+
+/*! \fn JsonArray::iterator JsonArray::end()
+
+ Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item
+ after the last item in the array.
+
+ \sa begin(), constEnd()
+*/
+
+/*! \fn const_iterator JsonArray::end() const
+
+ \overload
+*/
+
+/*! \fn JsonArray::const_iterator JsonArray::constEnd() const
+
+ Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary
+ item after the last item in the array.
+
+ \sa constBegin(), end()
+*/
+
+/*! \fn void JsonArray::push_back(const JsonValue &value)
+
+ This function is provided for STL compatibility. It is equivalent
+ to \l{JsonArray::append()}{append(value)} and will append \a value to the array.
+*/
+
+/*! \fn void JsonArray::push_front(const JsonValue &value)
+
+ This function is provided for STL compatibility. It is equivalent
+ to \l{JsonArray::prepend()}{prepend(value)} and will prepend \a value to the array.
+*/
+
+/*! \fn void JsonArray::pop_front()
+
+ This function is provided for STL compatibility. It is equivalent
+ to removeFirst(). The array must not be empty. If the array can be
+ empty, call isEmpty() before calling this function.
+*/
+
+/*! \fn void JsonArray::pop_back()
+
+ This function is provided for STL compatibility. It is equivalent
+ to removeLast(). The array must not be empty. If the array can be
+ empty, call isEmpty() before calling this function.
+*/
+
+/*! \fn bool JsonArray::empty() const
+
+ This function is provided for STL compatibility. It is equivalent
+ to isEmpty() and returns \c true if the array is empty.
+*/
+
+/*! \class JsonArray::iterator
+ \inmodule QtCore
+ \brief The JsonArray::iterator class provides an STL-style non-const iterator for JsonArray.
+
+ JsonArray::iterator allows you to iterate over a JsonArray
+ and to modify the array item associated with the
+ iterator. If you want to iterate over a const JsonArray, use
+ JsonArray::const_iterator instead. It is generally a good practice to
+ use JsonArray::const_iterator on a non-const JsonArray as well, unless
+ you need to change the JsonArray through the iterator. Const
+ iterators are slightly faster and improves code readability.
+
+ The default JsonArray::iterator constructor creates an uninitialized
+ iterator. You must initialize it using a JsonArray function like
+ JsonArray::begin(), JsonArray::end(), or JsonArray::insert() before you can
+ start iterating.
+
+ Most JsonArray functions accept an integer index rather than an
+ iterator. For that reason, iterators are rarely useful in
+ connection with JsonArray. One place where STL-style iterators do
+ make sense is as arguments to \l{generic algorithms}.
+
+ Multiple iterators can be used on the same array. However, be
+ aware that any non-const function call performed on the JsonArray
+ will render all existing iterators undefined.
+
+ \sa JsonArray::const_iterator
+*/
+
+/*! \typedef JsonArray::iterator::iterator_category
+
+ A synonym for \e {std::random_access_iterator_tag} indicating
+ this iterator is a random access iterator.
+*/
+
+/*! \typedef JsonArray::iterator::difference_type
+
+ \internal
+*/
+
+/*! \typedef JsonArray::iterator::value_type
+
+ \internal
+*/
+
+/*! \typedef JsonArray::iterator::reference
+
+ \internal
+*/
+
+/*! \typedef JsonArray::iterator::pointer
+
+ \internal
+*/
+
+/*! \fn JsonArray::iterator::iterator()
+
+ Constructs an uninitialized iterator.
+
+ Functions like operator*() and operator++() should not be called
+ on an uninitialized iterator. Use operator=() to assign a value
+ to it before using it.
+
+ \sa JsonArray::begin(), JsonArray::end()
+*/
+
+/*! \fn JsonArray::iterator::iterator(JsonArray *array, int index)
+ \internal
+*/
+
+/*! \fn JsonValueRef JsonArray::iterator::operator*() const
+
+
+ Returns a modifiable reference to the current item.
+
+ You can change the value of an item by using operator*() on the
+ left side of an assignment.
+
+ The return value is of type JsonValueRef, a helper class for JsonArray
+ and JsonObject. When you get an object of type JsonValueRef, you can
+ use it as if it were a reference to a JsonValue. If you assign to it,
+ the assignment will apply to the character in the JsonArray of JsonObject
+ from which you got the reference.
+*/
+
+/*! \fn JsonValueRef *JsonArray::iterator::operator->() const
+
+ Returns a pointer to a modifiable reference to the current item.
+*/
+
+/*! \fn JsonValueRef JsonArray::iterator::operator[](int j) const
+
+ Returns a modifiable reference to the item at offset \a j from the
+ item pointed to by this iterator (the item at position \c{*this + j}).
+
+ This function is provided to make JsonArray iterators behave like C++
+ pointers.
+
+ The return value is of type JsonValueRef, a helper class for JsonArray
+ and JsonObject. When you get an object of type JsonValueRef, you can
+ use it as if it were a reference to a JsonValue. If you assign to it,
+ the assignment will apply to the character in the JsonArray of JsonObject
+ from which you got the reference.
+
+ \sa operator+()
+*/
+
+/*!
+ \fn bool JsonArray::iterator::operator==(const iterator &other) const
+ \fn bool JsonArray::iterator::operator==(const const_iterator &other) const
+
+ Returns \c true if \a other points to the same item as this
+ iterator; otherwise returns \c false.
+
+ \sa operator!=()
+*/
+
+/*!
+ \fn bool JsonArray::iterator::operator!=(const iterator &other) const
+ \fn bool JsonArray::iterator::operator!=(const const_iterator &other) const
+
+ Returns \c true if \a other points to a different item than this
+ iterator; otherwise returns \c false.
+
+ \sa operator==()
+*/
+
+/*!
+ \fn bool JsonArray::iterator::operator<(const iterator& other) const
+ \fn bool JsonArray::iterator::operator<(const const_iterator& other) const
+
+ Returns \c true if the item pointed to by this iterator is less than
+ the item pointed to by the \a other iterator.
+*/
+
+/*!
+ \fn bool JsonArray::iterator::operator<=(const iterator& other) const
+ \fn bool JsonArray::iterator::operator<=(const const_iterator& other) const
+
+ Returns \c true if the item pointed to by this iterator is less than
+ or equal to the item pointed to by the \a other iterator.
+*/
+
+/*!
+ \fn bool JsonArray::iterator::operator>(const iterator& other) const
+ \fn bool JsonArray::iterator::operator>(const const_iterator& other) const
+
+ Returns \c true if the item pointed to by this iterator is greater
+ than the item pointed to by the \a other iterator.
+*/
+
+/*!
+ \fn bool JsonArray::iterator::operator>=(const iterator& other) const
+ \fn bool JsonArray::iterator::operator>=(const const_iterator& other) const
+
+ Returns \c true if the item pointed to by this iterator is greater
+ than or equal to the item pointed to by the \a other iterator.
+*/
+
+/*! \fn JsonArray::iterator &JsonArray::iterator::operator++()
+
+ The prefix ++ operator, \c{++it}, advances the iterator to the
+ next item in the array and returns an iterator to the new current
+ item.
+
+ Calling this function on JsonArray::end() leads to undefined results.
+
+ \sa operator--()
+*/
+
+/*! \fn JsonArray::iterator JsonArray::iterator::operator++(int)
+
+ \overload
+
+ The postfix ++ operator, \c{it++}, advances the iterator to the
+ next item in the array and returns an iterator to the previously
+ current item.
+*/
+
+/*! \fn JsonArray::iterator &JsonArray::iterator::operator--()
+
+ The prefix -- operator, \c{--it}, makes the preceding item
+ current and returns an iterator to the new current item.
+
+ Calling this function on JsonArray::begin() leads to undefined results.
+
+ \sa operator++()
+*/
+
+/*! \fn JsonArray::iterator JsonArray::iterator::operator--(int)
+
+ \overload
+
+ The postfix -- operator, \c{it--}, makes the preceding item
+ current and returns an iterator to the previously current item.
+*/
+
+/*! \fn JsonArray::iterator &JsonArray::iterator::operator+=(int j)
+
+ Advances the iterator by \a j items. If \a j is negative, the
+ iterator goes backward.
+
+ \sa operator-=(), operator+()
+*/
+
+/*! \fn JsonArray::iterator &JsonArray::iterator::operator-=(int j)
+
+ Makes the iterator go back by \a j items. If \a j is negative,
+ the iterator goes forward.
+
+ \sa operator+=(), operator-()
+*/
+
+/*! \fn JsonArray::iterator JsonArray::iterator::operator+(int j) const
+
+ Returns an iterator to the item at \a j positions forward from
+ this iterator. If \a j is negative, the iterator goes backward.
+
+ \sa operator-(), operator+=()
+*/
+
+/*! \fn JsonArray::iterator JsonArray::iterator::operator-(int j) const
+
+ Returns an iterator to the item at \a j positions backward from
+ this iterator. If \a j is negative, the iterator goes forward.
+
+ \sa operator+(), operator-=()
+*/
+
+/*! \fn int JsonArray::iterator::operator-(iterator other) const
+
+ Returns the number of items between the item pointed to by \a
+ other and the item pointed to by this iterator.
+*/
+
+/*! \class JsonArray::const_iterator
+ \inmodule QtCore
+ \brief The JsonArray::const_iterator class provides an STL-style const iterator for JsonArray.
+
+ JsonArray::const_iterator allows you to iterate over a
+ JsonArray. If you want to modify the JsonArray as
+ you iterate over it, use JsonArray::iterator instead. It is generally a
+ good practice to use JsonArray::const_iterator on a non-const JsonArray
+ as well, unless you need to change the JsonArray through the
+ iterator. Const iterators are slightly faster and improves
+ code readability.
+
+ The default JsonArray::const_iterator constructor creates an
+ uninitialized iterator. You must initialize it using a JsonArray
+ function like JsonArray::constBegin(), JsonArray::constEnd(), or
+ JsonArray::insert() before you can start iterating.
+
+ Most JsonArray functions accept an integer index rather than an
+ iterator. For that reason, iterators are rarely useful in
+ connection with JsonArray. One place where STL-style iterators do
+ make sense is as arguments to \l{generic algorithms}.
+
+ Multiple iterators can be used on the same array. However, be
+ aware that any non-const function call performed on the JsonArray
+ will render all existing iterators undefined.
+
+ \sa JsonArray::iterator
+*/
+
+/*! \fn JsonArray::const_iterator::const_iterator()
+
+ Constructs an uninitialized iterator.
+
+ Functions like operator*() and operator++() should not be called
+ on an uninitialized iterator. Use operator=() to assign a value
+ to it before using it.
+
+ \sa JsonArray::constBegin(), JsonArray::constEnd()
+*/
+
+/*! \fn JsonArray::const_iterator::const_iterator(const JsonArray *array, int index)
+ \internal
+*/
+
+/*! \typedef JsonArray::const_iterator::iterator_category
+
+ A synonym for \e {std::random_access_iterator_tag} indicating
+ this iterator is a random access iterator.
+*/
+
+/*! \typedef JsonArray::const_iterator::difference_type
+
+ \internal
+*/
+
+/*! \typedef JsonArray::const_iterator::value_type
+
+ \internal
+*/
+
+/*! \typedef JsonArray::const_iterator::reference
+
+ \internal
+*/
+
+/*! \typedef JsonArray::const_iterator::pointer
+
+ \internal
+*/
+
+/*! \fn JsonArray::const_iterator::const_iterator(const const_iterator &other)
+
+ Constructs a copy of \a other.
+*/
+
+/*! \fn JsonArray::const_iterator::const_iterator(const iterator &other)
+
+ Constructs a copy of \a other.
+*/
+
+/*! \fn JsonValue JsonArray::const_iterator::operator*() const
+
+ Returns the current item.
+*/
+
+/*! \fn JsonValue *JsonArray::const_iterator::operator->() const
+
+ Returns a pointer to the current item.
+*/
+
+/*! \fn JsonValue JsonArray::const_iterator::operator[](int j) const
+
+ Returns the item at offset \a j from the item pointed to by this iterator (the item at
+ position \c{*this + j}).
+
+ This function is provided to make JsonArray iterators behave like C++
+ pointers.
+
+ \sa operator+()
+*/
+
+/*! \fn bool JsonArray::const_iterator::operator==(const const_iterator &other) const
+
+ Returns \c true if \a other points to the same item as this
+ iterator; otherwise returns \c false.
+
+ \sa operator!=()
+*/
+
+/*! \fn bool JsonArray::const_iterator::operator!=(const const_iterator &other) const
+
+ Returns \c true if \a other points to a different item than this
+ iterator; otherwise returns \c false.
+
+ \sa operator==()
+*/
+
+/*!
+ \fn bool JsonArray::const_iterator::operator<(const const_iterator& other) const
+
+ Returns \c true if the item pointed to by this iterator is less than
+ the item pointed to by the \a other iterator.
+*/
+
+/*!
+ \fn bool JsonArray::const_iterator::operator<=(const const_iterator& other) const
+
+ Returns \c true if the item pointed to by this iterator is less than
+ or equal to the item pointed to by the \a other iterator.
+*/
+
+/*!
+ \fn bool JsonArray::const_iterator::operator>(const const_iterator& other) const
+
+ Returns \c true if the item pointed to by this iterator is greater
+ than the item pointed to by the \a other iterator.
+*/
+
+/*!
+ \fn bool JsonArray::const_iterator::operator>=(const const_iterator& other) const
+
+ Returns \c true if the item pointed to by this iterator is greater
+ than or equal to the item pointed to by the \a other iterator.
+*/
+
+/*! \fn JsonArray::const_iterator &JsonArray::const_iterator::operator++()
+
+ The prefix ++ operator, \c{++it}, advances the iterator to the
+ next item in the array and returns an iterator to the new current
+ item.
+
+ Calling this function on JsonArray::end() leads to undefined results.
+
+ \sa operator--()
+*/
+
+/*! \fn JsonArray::const_iterator JsonArray::const_iterator::operator++(int)
+
+ \overload
+
+ The postfix ++ operator, \c{it++}, advances the iterator to the
+ next item in the array and returns an iterator to the previously
+ current item.
+*/
+
+/*! \fn JsonArray::const_iterator &JsonArray::const_iterator::operator--()
+
+ The prefix -- operator, \c{--it}, makes the preceding item
+ current and returns an iterator to the new current item.
+
+ Calling this function on JsonArray::begin() leads to undefined results.
+
+ \sa operator++()
+*/
+
+/*! \fn JsonArray::const_iterator JsonArray::const_iterator::operator--(int)
+
+ \overload
+
+ The postfix -- operator, \c{it--}, makes the preceding item
+ current and returns an iterator to the previously current item.
+*/
+
+/*! \fn JsonArray::const_iterator &JsonArray::const_iterator::operator+=(int j)
+
+ Advances the iterator by \a j items. If \a j is negative, the
+ iterator goes backward.
+
+ \sa operator-=(), operator+()
+*/
+
+/*! \fn JsonArray::const_iterator &JsonArray::const_iterator::operator-=(int j)
+
+ Makes the iterator go back by \a j items. If \a j is negative,
+ the iterator goes forward.
+
+ \sa operator+=(), operator-()
+*/
+
+/*! \fn JsonArray::const_iterator JsonArray::const_iterator::operator+(int j) const
+
+ Returns an iterator to the item at \a j positions forward from
+ this iterator. If \a j is negative, the iterator goes backward.
+
+ \sa operator-(), operator+=()
+*/
+
+/*! \fn JsonArray::const_iterator JsonArray::const_iterator::operator-(int j) const
+
+ Returns an iterator to the item at \a j positions backward from
+ this iterator. If \a j is negative, the iterator goes forward.
+
+ \sa operator+(), operator-=()
+*/
+
+/*! \fn int JsonArray::const_iterator::operator-(const_iterator other) const
+
+ Returns the number of items between the item pointed to by \a
+ other and the item pointed to by this iterator.
+*/
+
+
+/*!
+ \internal
+ */
+void JsonArray::detach(uint32_t reserve)
+{
+ if (!d) {
+ d = new Internal::Data(reserve, JsonValue::Array);
+ a = static_cast<Internal::Array *>(d->header->root());
+ d->ref.ref();
+ return;
+ }
+ if (reserve == 0 && d->ref.load() == 1)
+ return;
+
+ Internal::Data *x = d->clone(a, reserve);
+ x->ref.ref();
+ if (!d->ref.deref())
+ delete d;
+ d = x;
+ a = static_cast<Internal::Array *>(d->header->root());
+}
+
+/*!
+ \internal
+ */
+void JsonArray::compact()
+{
+ if (!d || !d->compactionCounter)
+ return;
+
+ detach();
+ d->compact();
+ a = static_cast<Internal::Array *>(d->header->root());
+}
+
+/*!
+ \class JsonObject
+ \inmodule QtCore
+ \ingroup json
+ \ingroup shared
+ \reentrant
+ \since 5.0
+
+ \brief The JsonObject class encapsulates a JSON object.
+
+ A JSON object is a list of key value pairs, where the keys are unique strings
+ and the values are represented by a JsonValue.
+
+ A JsonObject can be converted to and from a QVariantMap. You can query the
+ number of (key, value) pairs with size(), insert(), and remove() entries from it
+ and iterate over its content using the standard C++ iterator pattern.
+
+ JsonObject is an implicitly shared class, and shares the data with the document
+ it has been created from as long as it is not being modified.
+
+ You can convert the object to and from text based JSON through JsonDocument.
+
+ \sa {JSON Support in Qt}, {JSON Save Game Example}
+*/
+
+/*!
+ \typedef JsonObject::Iterator
+
+ Qt-style synonym for JsonObject::iterator.
+*/
+
+/*!
+ \typedef JsonObject::ConstIterator
+
+ Qt-style synonym for JsonObject::const_iterator.
+*/
+
+/*!
+ \typedef JsonObject::key_type
+
+ Typedef for QString. Provided for STL compatibility.
+*/
+
+/*!
+ \typedef JsonObject::mapped_type
+
+ Typedef for JsonValue. Provided for STL compatibility.
+*/
+
+/*!
+ \typedef JsonObject::size_type
+
+ Typedef for int. Provided for STL compatibility.
+*/
+
+
+/*!
+ Constructs an empty JSON object.
+
+ \sa isEmpty()
+ */
+JsonObject::JsonObject()
+ : d(0), o(0)
+{
+}
+
+JsonObject::JsonObject(std::initializer_list<std::pair<std::string, JsonValue> > args)
+ : d(0), o(0)
+{
+ for (auto i = args.begin(); i != args.end(); ++i)
+ insert(i->first, i->second);
+}
+
+/*!
+ \fn JsonObject::JsonObject(std::initializer_list<QPair<QString, JsonValue> > args)
+ \since 5.4
+ Constructs a JsonObject instance initialized from \a args initialization list.
+ For example:
+ \code
+ JsonObject object
+ {
+ {"property1", 1},
+ {"property2", 2}
+ };
+ \endcode
+*/
+
+/*!
+ \internal
+ */
+JsonObject::JsonObject(Internal::Data *data, Internal::Object *object)
+ : d(data), o(object)
+{
+ // assert(d);
+ // assert(o);
+ d->ref.ref();
+}
+
+/*!
+ This method replaces part of the JsonObject(std::initializer_list<QPair<QString, JsonValue>> args) body.
+ The constructor needs to be inline, but we do not want to leak implementation details
+ of this class.
+ \note this method is called for an uninitialized object
+ \internal
+ */
+
+/*!
+ Destroys the object.
+ */
+JsonObject::~JsonObject()
+{
+ if (d && !d->ref.deref())
+ delete d;
+}
+
+/*!
+ Creates a copy of \a other.
+
+ Since JsonObject is implicitly shared, the copy is shallow
+ as long as the object does not get modified.
+ */
+JsonObject::JsonObject(const JsonObject &other)
+{
+ d = other.d;
+ o = other.o;
+ if (d)
+ d->ref.ref();
+}
+
+/*!
+ Assigns \a other to this object.
+ */
+JsonObject &JsonObject::operator=(const JsonObject &other)
+{
+ if (d != other.d) {
+ if (d && !d->ref.deref())
+ delete d;
+ d = other.d;
+ if (d)
+ d->ref.ref();
+ }
+ o = other.o;
+
+ return *this;
+}
+
+/*!
+ Returns a list of all keys in this object.
+
+ The list is sorted lexographically.
+ */
+JsonObject::Keys JsonObject::keys() const
+{
+ Keys keys;
+ if (!d)
+ return keys;
+
+ keys.reserve(o->length);
+ for (uint32_t i = 0; i < o->length; ++i) {
+ Internal::Entry *e = o->entryAt(i);
+ keys.push_back(e->key().data());
+ }
+
+ return keys;
+}
+
+/*!
+ Returns the number of (key, value) pairs stored in the object.
+ */
+int JsonObject::size() const
+{
+ if (!d)
+ return 0;
+
+ return o->length;
+}
+
+/*!
+ Returns \c true if the object is empty. This is the same as size() == 0.
+
+ \sa size()
+ */
+bool JsonObject::isEmpty() const
+{
+ if (!d)
+ return true;
+
+ return !o->length;
+}
+
+/*!
+ Returns a JsonValue representing the value for the key \a key.
+
+ The returned JsonValue is JsonValue::Undefined if the key does not exist.
+
+ \sa JsonValue, JsonValue::isUndefined()
+ */
+JsonValue JsonObject::value(const std::string &key) const
+{
+ if (!d)
+ return JsonValue(JsonValue::Undefined);
+
+ bool keyExists;
+ int i = o->indexOf(key, &keyExists);
+ if (!keyExists)
+ return JsonValue(JsonValue::Undefined);
+ return JsonValue(d, o, o->entryAt(i)->value);
+}
+
+/*!
+ Returns a JsonValue representing the value for the key \a key.
+
+ This does the same as value().
+
+ The returned JsonValue is JsonValue::Undefined if the key does not exist.
+
+ \sa value(), JsonValue, JsonValue::isUndefined()
+ */
+JsonValue JsonObject::operator[](const std::string &key) const
+{
+ return value(key);
+}
+
+/*!
+ Returns a reference to the value for \a key.
+
+ The return value is of type JsonValueRef, a helper class for JsonArray
+ and JsonObject. When you get an object of type JsonValueRef, you can
+ use it as if it were a reference to a JsonValue. If you assign to it,
+ the assignment will apply to the element in the JsonArray or JsonObject
+ from which you got the reference.
+
+ \sa value()
+ */
+JsonValueRef JsonObject::operator[](const std::string &key)
+{
+ // ### somewhat inefficient, as we lookup the key twice if it doesn't yet exist
+ bool keyExists = false;
+ int index = o ? o->indexOf(key, &keyExists) : -1;
+ if (!keyExists) {
+ iterator i = insert(key, JsonValue());
+ index = i.i;
+ }
+ return JsonValueRef(this, index);
+}
+
+/*!
+ Inserts a new item with the key \a key and a value of \a value.
+
+ If there is already an item with the key \a key, then that item's value
+ is replaced with \a value.
+
+ Returns an iterator pointing to the inserted item.
+
+ If the value is JsonValue::Undefined, it will cause the key to get removed
+ from the object. The returned iterator will then point to end().
+
+ \sa remove(), take(), JsonObject::iterator, end()
+ */
+JsonObject::iterator JsonObject::insert(const std::string &key, const JsonValue &value)
+{
+ if (value.t == JsonValue::Undefined) {
+ remove(key);
+ return end();
+ }
+ JsonValue val = value;
+
+ bool isIntValue;
+ int valueSize = Internal::Value::requiredStorage(val, &isIntValue);
+
+ int valueOffset = sizeof(Internal::Entry) + Internal::qStringSize(key);
+ int requiredSize = valueOffset + valueSize;
+
+ detach(requiredSize + sizeof(Internal::offset)); // offset for the new index entry
+
+ if (!o->length)
+ o->tableOffset = sizeof(Internal::Object);
+
+ bool keyExists = false;
+ int pos = o->indexOf(key, &keyExists);
+ if (keyExists)
+ ++d->compactionCounter;
+
+ uint32_t off = o->reserveSpace(requiredSize, pos, 1, keyExists);
+ if (!off)
+ return end();
+
+ Internal::Entry *e = o->entryAt(pos);
+ e->value.type = val.t;
+ e->value.intValue = isIntValue;
+ e->value.value = Internal::Value::valueToStore(val, static_cast<uint32_t>((char *)e - (char *)o)
+ + valueOffset);
+ Internal::copyString((char *)(e + 1), key);
+ if (valueSize)
+ Internal::Value::copyData(val, (char *)e + valueOffset, isIntValue);
+
+ if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u)
+ compact();
+
+ return iterator(this, pos);
+}
+
+/*!
+ Removes \a key from the object.
+
+ \sa insert(), take()
+ */
+void JsonObject::remove(const std::string &key)
+{
+ if (!d)
+ return;
+
+ bool keyExists;
+ int index = o->indexOf(key, &keyExists);
+ if (!keyExists)
+ return;
+
+ detach();
+ o->removeItems(index, 1);
+ ++d->compactionCounter;
+ if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u)
+ compact();
+}
+
+/*!
+ Removes \a key from the object.
+
+ Returns a JsonValue containing the value referenced by \a key.
+ If \a key was not contained in the object, the returned JsonValue
+ is JsonValue::Undefined.
+
+ \sa insert(), remove(), JsonValue
+ */
+JsonValue JsonObject::take(const std::string &key)
+{
+ if (!o)
+ return JsonValue(JsonValue::Undefined);
+
+ bool keyExists;
+ int index = o->indexOf(key, &keyExists);
+ if (!keyExists)
+ return JsonValue(JsonValue::Undefined);
+
+ JsonValue v(d, o, o->entryAt(index)->value);
+ detach();
+ o->removeItems(index, 1);
+ ++d->compactionCounter;
+ if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u)
+ compact();
+
+ return v;
+}
+
+/*!
+ Returns \c true if the object contains key \a key.
+
+ \sa insert(), remove(), take()
+ */
+bool JsonObject::contains(const std::string &key) const
+{
+ if (!o)
+ return false;
+
+ bool keyExists;
+ o->indexOf(key, &keyExists);
+ return keyExists;
+}
+
+/*!
+ Returns \c true if \a other is equal to this object.
+ */
+bool JsonObject::operator==(const JsonObject &other) const
+{
+ if (o == other.o)
+ return true;
+
+ if (!o)
+ return !other.o->length;
+ if (!other.o)
+ return !o->length;
+ if (o->length != other.o->length)
+ return false;
+
+ for (uint32_t i = 0; i < o->length; ++i) {
+ Internal::Entry *e = o->entryAt(i);
+ JsonValue v(d, o, e->value);
+ if (other.value(e->key()) != v)
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ Returns \c true if \a other is not equal to this object.
+ */
+bool JsonObject::operator!=(const JsonObject &other) const
+{
+ return !(*this == other);
+}
+
+/*!
+ Removes the (key, value) pair pointed to by the iterator \a it
+ from the map, and returns an iterator to the next item in the
+ map.
+
+ \sa remove()
+ */
+JsonObject::iterator JsonObject::erase(JsonObject::iterator it)
+{
+ // assert(d && d->ref.load() == 1);
+ if (it.o != this || it.i < 0 || it.i >= (int)o->length)
+ return iterator(this, o->length);
+
+ int index = it.i;
+
+ o->removeItems(index, 1);
+ ++d->compactionCounter;
+ if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u)
+ compact();
+
+ // iterator hasn't changed
+ return it;
+}
+
+/*!
+ Returns an iterator pointing to the item with key \a key in the
+ map.
+
+ If the map contains no item with key \a key, the function
+ returns end().
+ */
+JsonObject::iterator JsonObject::find(const std::string &key)
+{
+ bool keyExists = false;
+ int index = o ? o->indexOf(key, &keyExists) : 0;
+ if (!keyExists)
+ return end();
+ detach();
+ return iterator(this, index);
+}
+
+/*! \fn JsonObject::const_iterator JsonObject::find(const QString &key) const
+
+ \overload
+*/
+
+/*!
+ Returns a const iterator pointing to the item with key \a key in the
+ map.
+
+ If the map contains no item with key \a key, the function
+ returns constEnd().
+ */
+JsonObject::const_iterator JsonObject::constFind(const std::string &key) const
+{
+ bool keyExists = false;
+ int index = o ? o->indexOf(key, &keyExists) : 0;
+ if (!keyExists)
+ return end();
+ return const_iterator(this, index);
+}
+
+/*! \fn int JsonObject::count() const
+
+ \overload
+
+ Same as size().
+*/
+
+/*! \fn int JsonObject::length() const
+
+ \overload
+
+ Same as size().
+*/
+
+/*! \fn JsonObject::iterator JsonObject::begin()
+
+ Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in
+ the object.
+
+ \sa constBegin(), end()
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::begin() const
+
+ \overload
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::constBegin() const
+
+ Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item
+ in the object.
+
+ \sa begin(), constEnd()
+*/
+
+/*! \fn JsonObject::iterator JsonObject::end()
+
+ Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item
+ after the last item in the object.
+
+ \sa begin(), constEnd()
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::end() const
+
+ \overload
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::constEnd() const
+
+ Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary
+ item after the last item in the object.
+
+ \sa constBegin(), end()
+*/
+
+/*!
+ \fn bool JsonObject::empty() const
+
+ This function is provided for STL compatibility. It is equivalent
+ to isEmpty(), returning \c true if the object is empty; otherwise
+ returning \c false.
+*/
+
+/*! \class JsonObject::iterator
+ \inmodule QtCore
+ \ingroup json
+ \reentrant
+ \since 5.0
+
+ \brief The JsonObject::iterator class provides an STL-style non-const iterator for JsonObject.
+
+ JsonObject::iterator allows you to iterate over a JsonObject
+ and to modify the value (but not the key) stored under
+ a particular key. If you want to iterate over a const JsonObject, you
+ should use JsonObject::const_iterator. It is generally good practice to
+ use JsonObject::const_iterator on a non-const JsonObject as well, unless you
+ need to change the JsonObject through the iterator. Const iterators are
+ slightly faster, and improve code readability.
+
+ The default JsonObject::iterator constructor creates an uninitialized
+ iterator. You must initialize it using a JsonObject function like
+ JsonObject::begin(), JsonObject::end(), or JsonObject::find() before you can
+ start iterating.
+
+ Multiple iterators can be used on the same object. Existing iterators will however
+ become dangling once the object gets modified.
+
+ \sa JsonObject::const_iterator, {JSON Support in Qt}, {JSON Save Game Example}
+*/
+
+/*! \typedef JsonObject::iterator::difference_type
+
+ \internal
+*/
+
+/*! \typedef JsonObject::iterator::iterator_category
+
+ A synonym for \e {std::bidirectional_iterator_tag} indicating
+ this iterator is a bidirectional iterator.
+*/
+
+/*! \typedef JsonObject::iterator::reference
+
+ \internal
+*/
+
+/*! \typedef JsonObject::iterator::value_type
+
+ \internal
+*/
+
+/*! \fn JsonObject::iterator::iterator()
+
+ Constructs an uninitialized iterator.
+
+ Functions like key(), value(), and operator++() must not be
+ called on an uninitialized iterator. Use operator=() to assign a
+ value to it before using it.
+
+ \sa JsonObject::begin(), JsonObject::end()
+*/
+
+/*! \fn JsonObject::iterator::iterator(JsonObject *obj, int index)
+ \internal
+*/
+
+/*! \fn QString JsonObject::iterator::key() const
+
+ Returns the current item's key.
+
+ There is no direct way of changing an item's key through an
+ iterator, although it can be done by calling JsonObject::erase()
+ followed by JsonObject::insert().
+
+ \sa value()
+*/
+
+/*! \fn JsonValueRef JsonObject::iterator::value() const
+
+ Returns a modifiable reference to the current item's value.
+
+ You can change the value of an item by using value() on
+ the left side of an assignment.
+
+ The return value is of type JsonValueRef, a helper class for JsonArray
+ and JsonObject. When you get an object of type JsonValueRef, you can
+ use it as if it were a reference to a JsonValue. If you assign to it,
+ the assignment will apply to the element in the JsonArray or JsonObject
+ from which you got the reference.
+
+ \sa key(), operator*()
+*/
+
+/*! \fn JsonValueRef JsonObject::iterator::operator*() const
+
+ Returns a modifiable reference to the current item's value.
+
+ Same as value().
+
+ The return value is of type JsonValueRef, a helper class for JsonArray
+ and JsonObject. When you get an object of type JsonValueRef, you can
+ use it as if it were a reference to a JsonValue. If you assign to it,
+ the assignment will apply to the element in the JsonArray or JsonObject
+ from which you got the reference.
+
+ \sa key()
+*/
+
+/*! \fn JsonValueRef *JsonObject::iterator::operator->() const
+
+ Returns a pointer to a modifiable reference to the current item.
+*/
+
+/*!
+ \fn bool JsonObject::iterator::operator==(const iterator &other) const
+ \fn bool JsonObject::iterator::operator==(const const_iterator &other) const
+
+ Returns \c true if \a other points to the same item as this
+ iterator; otherwise returns \c false.
+
+ \sa operator!=()
+*/
+
+/*!
+ \fn bool JsonObject::iterator::operator!=(const iterator &other) const
+ \fn bool JsonObject::iterator::operator!=(const const_iterator &other) const
+
+ Returns \c true if \a other points to a different item than this
+ iterator; otherwise returns \c false.
+
+ \sa operator==()
+*/
+
+/*! \fn JsonObject::iterator JsonObject::iterator::operator++()
+
+ The prefix ++ operator, \c{++i}, advances the iterator to the
+ next item in the object and returns an iterator to the new current
+ item.
+
+ Calling this function on JsonObject::end() leads to undefined results.
+
+ \sa operator--()
+*/
+
+/*! \fn JsonObject::iterator JsonObject::iterator::operator++(int)
+
+ \overload
+
+ The postfix ++ operator, \c{i++}, advances the iterator to the
+ next item in the object and returns an iterator to the previously
+ current item.
+*/
+
+/*! \fn JsonObject::iterator JsonObject::iterator::operator--()
+
+ The prefix -- operator, \c{--i}, makes the preceding item
+ current and returns an iterator pointing to the new current item.
+
+ Calling this function on JsonObject::begin() leads to undefined
+ results.
+
+ \sa operator++()
+*/
+
+/*! \fn JsonObject::iterator JsonObject::iterator::operator--(int)
+
+ \overload
+
+ The postfix -- operator, \c{i--}, makes the preceding item
+ current and returns an iterator pointing to the previously
+ current item.
+*/
+
+/*! \fn JsonObject::iterator JsonObject::iterator::operator+(int j) const
+
+ Returns an iterator to the item at \a j positions forward from
+ this iterator. If \a j is negative, the iterator goes backward.
+
+ \sa operator-()
+
+*/
+
+/*! \fn JsonObject::iterator JsonObject::iterator::operator-(int j) const
+
+ Returns an iterator to the item at \a j positions backward from
+ this iterator. If \a j is negative, the iterator goes forward.
+
+ \sa operator+()
+*/
+
+/*! \fn JsonObject::iterator &JsonObject::iterator::operator+=(int j)
+
+ Advances the iterator by \a j items. If \a j is negative, the
+ iterator goes backward.
+
+ \sa operator-=(), operator+()
+*/
+
+/*! \fn JsonObject::iterator &JsonObject::iterator::operator-=(int j)
+
+ Makes the iterator go back by \a j items. If \a j is negative,
+ the iterator goes forward.
+
+ \sa operator+=(), operator-()
+*/
+
+/*!
+ \class JsonObject::const_iterator
+ \inmodule QtCore
+ \ingroup json
+ \since 5.0
+ \brief The JsonObject::const_iterator class provides an STL-style const iterator for JsonObject.
+
+ JsonObject::const_iterator allows you to iterate over a JsonObject.
+ If you want to modify the JsonObject as you iterate
+ over it, you must use JsonObject::iterator instead. It is generally
+ good practice to use JsonObject::const_iterator on a non-const JsonObject as
+ well, unless you need to change the JsonObject through the iterator.
+ Const iterators are slightly faster and improve code
+ readability.
+
+ The default JsonObject::const_iterator constructor creates an
+ uninitialized iterator. You must initialize it using a JsonObject
+ function like JsonObject::constBegin(), JsonObject::constEnd(), or
+ JsonObject::find() before you can start iterating.
+
+ Multiple iterators can be used on the same object. Existing iterators
+ will however become dangling if the object gets modified.
+
+ \sa JsonObject::iterator, {JSON Support in Qt}, {JSON Save Game Example}
+*/
+
+/*! \typedef JsonObject::const_iterator::difference_type
+
+ \internal
+*/
+
+/*! \typedef JsonObject::const_iterator::iterator_category
+
+ A synonym for \e {std::bidirectional_iterator_tag} indicating
+ this iterator is a bidirectional iterator.
+*/
+
+/*! \typedef JsonObject::const_iterator::reference
+
+ \internal
+*/
+
+/*! \typedef JsonObject::const_iterator::value_type
+
+ \internal
+*/
+
+/*! \fn JsonObject::const_iterator::const_iterator()
+
+ Constructs an uninitialized iterator.
+
+ Functions like key(), value(), and operator++() must not be
+ called on an uninitialized iterator. Use operator=() to assign a
+ value to it before using it.
+
+ \sa JsonObject::constBegin(), JsonObject::constEnd()
+*/
+
+/*! \fn JsonObject::const_iterator::const_iterator(const JsonObject *obj, int index)
+ \internal
+*/
+
+/*! \fn JsonObject::const_iterator::const_iterator(const iterator &other)
+
+ Constructs a copy of \a other.
+*/
+
+/*! \fn QString JsonObject::const_iterator::key() const
+
+ Returns the current item's key.
+
+ \sa value()
+*/
+
+/*! \fn JsonValue JsonObject::const_iterator::value() const
+
+ Returns the current item's value.
+
+ \sa key(), operator*()
+*/
+
+/*! \fn JsonValue JsonObject::const_iterator::operator*() const
+
+ Returns the current item's value.
+
+ Same as value().
+
+ \sa key()
+*/
+
+/*! \fn JsonValue *JsonObject::const_iterator::operator->() const
+
+ Returns a pointer to the current item.
+*/
+
+/*! \fn bool JsonObject::const_iterator::operator==(const const_iterator &other) const
+ \fn bool JsonObject::const_iterator::operator==(const iterator &other) const
+
+ Returns \c true if \a other points to the same item as this
+ iterator; otherwise returns \c false.
+
+ \sa operator!=()
+*/
+
+/*! \fn bool JsonObject::const_iterator::operator!=(const const_iterator &other) const
+ \fn bool JsonObject::const_iterator::operator!=(const iterator &other) const
+
+ Returns \c true if \a other points to a different item than this
+ iterator; otherwise returns \c false.
+
+ \sa operator==()
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator++()
+
+ The prefix ++ operator, \c{++i}, advances the iterator to the
+ next item in the object and returns an iterator to the new current
+ item.
+
+ Calling this function on JsonObject::end() leads to undefined results.
+
+ \sa operator--()
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator++(int)
+
+ \overload
+
+ The postfix ++ operator, \c{i++}, advances the iterator to the
+ next item in the object and returns an iterator to the previously
+ current item.
+*/
+
+/*! \fn JsonObject::const_iterator &JsonObject::const_iterator::operator--()
+
+ The prefix -- operator, \c{--i}, makes the preceding item
+ current and returns an iterator pointing to the new current item.
+
+ Calling this function on JsonObject::begin() leads to undefined
+ results.
+
+ \sa operator++()
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator--(int)
+
+ \overload
+
+ The postfix -- operator, \c{i--}, makes the preceding item
+ current and returns an iterator pointing to the previously
+ current item.
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator+(int j) const
+
+ Returns an iterator to the item at \a j positions forward from
+ this iterator. If \a j is negative, the iterator goes backward.
+
+ This operation can be slow for large \a j values.
+
+ \sa operator-()
+*/
+
+/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator-(int j) const
+
+ Returns an iterator to the item at \a j positions backward from
+ this iterator. If \a j is negative, the iterator goes forward.
+
+ This operation can be slow for large \a j values.
+
+ \sa operator+()
+*/
+
+/*! \fn JsonObject::const_iterator &JsonObject::const_iterator::operator+=(int j)
+
+ Advances the iterator by \a j items. If \a j is negative, the
+ iterator goes backward.
+
+ This operation can be slow for large \a j values.
+
+ \sa operator-=(), operator+()
+*/
+
+/*! \fn JsonObject::const_iterator &JsonObject::const_iterator::operator-=(int j)
+
+ Makes the iterator go back by \a j items. If \a j is negative,
+ the iterator goes forward.
+
+ This operation can be slow for large \a j values.
+
+ \sa operator+=(), operator-()
+*/
+
+
+/*!
+ \internal
+ */
+void JsonObject::detach(uint32_t reserve)
+{
+ if (!d) {
+ d = new Internal::Data(reserve, JsonValue::Object);
+ o = static_cast<Internal::Object *>(d->header->root());
+ d->ref.ref();
+ return;
+ }
+ if (reserve == 0 && d->ref.load() == 1)
+ return;
+
+ Internal::Data *x = d->clone(o, reserve);
+ x->ref.ref();
+ if (!d->ref.deref())
+ delete d;
+ d = x;
+ o = static_cast<Internal::Object *>(d->header->root());
+}
+
+/*!
+ \internal
+ */
+void JsonObject::compact()
+{
+ if (!d || !d->compactionCounter)
+ return;
+
+ detach();
+ d->compact();
+ o = static_cast<Internal::Object *>(d->header->root());
+}
+
+/*!
+ \internal
+ */
+std::string JsonObject::keyAt(int i) const
+{
+ // assert(o && i >= 0 && i < (int)o->length);
+
+ Internal::Entry *e = o->entryAt(i);
+ return e->key();
+}
+
+/*!
+ \internal
+ */
+JsonValue JsonObject::valueAt(int i) const
+{
+ if (!o || i < 0 || i >= (int)o->length)
+ return JsonValue(JsonValue::Undefined);
+
+ Internal::Entry *e = o->entryAt(i);
+ return JsonValue(d, o, e->value);
+}
+
+/*!
+ \internal
+ */
+void JsonObject::setValueAt(int i, const JsonValue &val)
+{
+ // assert(o && i >= 0 && i < (int)o->length);
+
+ Internal::Entry *e = o->entryAt(i);
+ insert(e->key(), val);
+}
+
+
+/*! \class JsonDocument
+ \inmodule QtCore
+ \ingroup json
+ \ingroup shared
+ \reentrant
+ \since 5.0
+
+ \brief The JsonDocument class provides a way to read and write JSON documents.
+
+ JsonDocument is a class that wraps a complete JSON document and can read and
+ write this document both from a UTF-8 encoded text based representation as well
+ as Qt's own binary format.
+
+ A JSON document can be converted from its text-based representation to a JsonDocument
+ using JsonDocument::fromJson(). toJson() converts it back to text. The parser is very
+ fast and efficient and converts the JSON to the binary representation used by Qt.
+
+ Validity of the parsed document can be queried with !isNull()
+
+ A document can be queried as to whether it contains an array or an object using isArray()
+ and isObject(). The array or object contained in the document can be retrieved using
+ array() or object() and then read or manipulated.
+
+ A document can also be created from a stored binary representation using fromBinaryData() or
+ fromRawData().
+
+ \sa {JSON Support in Qt}, {JSON Save Game Example}
+*/
+
+/*!
+ * Constructs an empty and invalid document.
+ */
+JsonDocument::JsonDocument()
+ : d(0)
+{
+}
+
+/*!
+ * Creates a JsonDocument from \a object.
+ */
+JsonDocument::JsonDocument(const JsonObject &object)
+ : d(0)
+{
+ setObject(object);
+}
+
+/*!
+ * Constructs a JsonDocument from \a array.
+ */
+JsonDocument::JsonDocument(const JsonArray &array)
+ : d(0)
+{
+ setArray(array);
+}
+
+/*!
+ \internal
+ */
+JsonDocument::JsonDocument(Internal::Data *data)
+ : d(data)
+{
+ // assert(d);
+ d->ref.ref();
+}
+
+/*!
+ Deletes the document.
+
+ Binary data set with fromRawData is not freed.
+ */
+JsonDocument::~JsonDocument()
+{
+ if (d && !d->ref.deref())
+ delete d;
+}
+
+/*!
+ * Creates a copy of the \a other document.
+ */
+JsonDocument::JsonDocument(const JsonDocument &other)
+{
+ d = other.d;
+ if (d)
+ d->ref.ref();
+}
+
+/*!
+ * Assigns the \a other document to this JsonDocument.
+ * Returns a reference to this object.
+ */
+JsonDocument &JsonDocument::operator=(const JsonDocument &other)
+{
+ if (d != other.d) {
+ if (d && !d->ref.deref())
+ delete d;
+ d = other.d;
+ if (d)
+ d->ref.ref();
+ }
+
+ return *this;
+}
+
+/*! \enum JsonDocument::DataValidation
+
+ This value is used to tell JsonDocument whether to validate the binary data
+ when converting to a JsonDocument using fromBinaryData() or fromRawData().
+
+ \value Validate Validate the data before using it. This is the default.
+ \value BypassValidation Bypasses data validation. Only use if you received the
+ data from a trusted place and know it's valid, as using of invalid data can crash
+ the application.
+ */
+
+/*!
+ Creates a JsonDocument that uses the first \a size bytes from
+ \a data. It assumes \a data contains a binary encoded JSON document.
+ The created document does not take ownership of \a data and the caller
+ has to guarantee that \a data will not be deleted or modified as long as
+ any JsonDocument, JsonObject or JsonArray still references the data.
+
+ \a data has to be aligned to a 4 byte boundary.
+
+ \a validation decides whether the data is checked for validity before being used.
+ By default the data is validated. If the \a data is not valid, the method returns
+ a null document.
+
+ Returns a JsonDocument representing the data.
+
+ \sa rawData(), fromBinaryData(), isNull(), DataValidation
+ */
+JsonDocument JsonDocument::fromRawData(const char *data, int size, DataValidation validation)
+{
+ if (std::uintptr_t(data) & 3) {
+ std::cerr <<"JsonDocument::fromRawData: data has to have 4 byte alignment\n";
+ return JsonDocument();
+ }
+
+ Internal::Data *d = new Internal::Data((char *)data, size);
+ d->ownsData = false;
+
+ if (validation != BypassValidation && !d->valid()) {
+ delete d;
+ return JsonDocument();
+ }
+
+ return JsonDocument(d);
+}
+
+/*!
+ Returns the raw binary representation of the data
+ \a size will contain the size of the returned data.
+
+ This method is useful to e.g. stream the JSON document
+ in it's binary form to a file.
+ */
+const char *JsonDocument::rawData(int *size) const
+{
+ if (!d) {
+ *size = 0;
+ return 0;
+ }
+ *size = d->alloc;
+ return d->rawData;
+}
+
+/*!
+ Creates a JsonDocument from \a data.
+
+ \a validation decides whether the data is checked for validity before being used.
+ By default the data is validated. If the \a data is not valid, the method returns
+ a null document.
+
+ \sa toBinaryData(), fromRawData(), isNull(), DataValidation
+ */
+JsonDocument JsonDocument::fromBinaryData(const std::string &data, DataValidation validation)
+{
+ if (data.size() < (int)(sizeof(Internal::Header) + sizeof(Internal::Base)))
+ return JsonDocument();
+
+ Internal::Header h;
+ memcpy(&h, data.data(), sizeof(Internal::Header));
+ Internal::Base root;
+ memcpy(&root, data.data() + sizeof(Internal::Header), sizeof(Internal::Base));
+
+ // do basic checks here, so we don't try to allocate more memory than we can.
+ if (h.tag != JsonDocument::BinaryFormatTag || h.version != 1u ||
+ sizeof(Internal::Header) + root.size > (uint32_t)data.size())
+ return JsonDocument();
+
+ const uint32_t size = sizeof(Internal::Header) + root.size;
+ char *raw = (char *)malloc(size);
+ if (!raw)
+ return JsonDocument();
+
+ memcpy(raw, data.data(), size);
+ Internal::Data *d = new Internal::Data(raw, size);
+
+ if (validation != BypassValidation && !d->valid()) {
+ delete d;
+ return JsonDocument();
+ }
+
+ return JsonDocument(d);
+}
+
+/*!
+ \enum JsonDocument::JsonFormat
+
+ This value defines the format of the JSON byte array produced
+ when converting to a JsonDocument using toJson().
+
+ \value Indented Defines human readable output as follows:
+ \code
+ {
+ "Array": [
+ true,
+ 999,
+ "string"
+ ],
+ "Key": "Value",
+ "null": null
+ }
+ \endcode
+
+ \value Compact Defines a compact output as follows:
+ \code
+ {"Array":[true,999,"string"],"Key":"Value","null":null}
+ \endcode
+ */
+
+/*!
+ Converts the JsonDocument to a UTF-8 encoded JSON document in the provided \a format.
+
+ \sa fromJson(), JsonFormat
+ */
+#ifndef QT_JSON_READONLY
+std::string JsonDocument::toJson(JsonFormat format) const
+{
+ std::string json;
+
+ if (!d)
+ return json;
+
+ if (d->header->root()->isArray())
+ Internal::arrayToJson(static_cast<Internal::Array *>(d->header->root()), json, 0, (format == Compact));
+ else
+ Internal::objectToJson(static_cast<Internal::Object *>(d->header->root()), json, 0, (format == Compact));
+
+ return json;
+}
+#endif
+
+/*!
+ Parses a UTF-8 encoded JSON document and creates a JsonDocument
+ from it.
+
+ \a json contains the json document to be parsed.
+
+ The optional \a error variable can be used to pass in a JsonParseError data
+ structure that will contain information about possible errors encountered during
+ parsing.
+
+ \sa toJson(), JsonParseError
+ */
+JsonDocument JsonDocument::fromJson(const std::string &json, JsonParseError *error)
+{
+ Internal::Parser parser(json.data(), static_cast<int>(json.length()));
+ return parser.parse(error);
+}
+
+/*!
+ Returns \c true if the document doesn't contain any data.
+ */
+bool JsonDocument::isEmpty() const
+{
+ if (!d)
+ return true;
+
+ return false;
+}
+
+/*!
+ Returns a binary representation of the document.
+
+ The binary representation is also the native format used internally in Qt,
+ and is very efficient and fast to convert to and from.
+
+ The binary format can be stored on disk and interchanged with other applications
+ or computers. fromBinaryData() can be used to convert it back into a
+ JSON document.
+
+ \sa fromBinaryData()
+ */
+std::string JsonDocument::toBinaryData() const
+{
+ if (!d || !d->rawData)
+ return std::string();
+
+ return std::string(d->rawData, d->header->root()->size + sizeof(Internal::Header));
+}
+
+/*!
+ Returns \c true if the document contains an array.
+
+ \sa array(), isObject()
+ */
+bool JsonDocument::isArray() const
+{
+ if (!d)
+ return false;
+
+ Internal::Header *h = (Internal::Header *)d->rawData;
+ return h->root()->isArray();
+}
+
+/*!
+ Returns \c true if the document contains an object.
+
+ \sa object(), isArray()
+ */
+bool JsonDocument::isObject() const
+{
+ if (!d)
+ return false;
+
+ Internal::Header *h = (Internal::Header *)d->rawData;
+ return h->root()->isObject();
+}
+
+/*!
+ Returns the JsonObject contained in the document.
+
+ Returns an empty object if the document contains an
+ array.
+
+ \sa isObject(), array(), setObject()
+ */
+JsonObject JsonDocument::object() const
+{
+ if (d) {
+ Internal::Base *b = d->header->root();
+ if (b->isObject())
+ return JsonObject(d, static_cast<Internal::Object *>(b));
+ }
+ return JsonObject();
+}
+
+/*!
+ Returns the JsonArray contained in the document.
+
+ Returns an empty array if the document contains an
+ object.
+
+ \sa isArray(), object(), setArray()
+ */
+JsonArray JsonDocument::array() const
+{
+ if (d) {
+ Internal::Base *b = d->header->root();
+ if (b->isArray())
+ return JsonArray(d, static_cast<Internal::Array *>(b));
+ }
+ return JsonArray();
+}
+
+/*!
+ Sets \a object as the main object of this document.
+
+ \sa setArray(), object()
+ */
+void JsonDocument::setObject(const JsonObject &object)
+{
+ if (d && !d->ref.deref())
+ delete d;
+
+ d = object.d;
+
+ if (!d) {
+ d = new Internal::Data(0, JsonValue::Object);
+ } else if (d->compactionCounter || object.o != d->header->root()) {
+ JsonObject o(object);
+ if (d->compactionCounter)
+ o.compact();
+ else
+ o.detach();
+ d = o.d;
+ d->ref.ref();
+ return;
+ }
+ d->ref.ref();
+}
+
+/*!
+ Sets \a array as the main object of this document.
+
+ \sa setObject(), array()
+ */
+void JsonDocument::setArray(const JsonArray &array)
+{
+ if (d && !d->ref.deref())
+ delete d;
+
+ d = array.d;
+
+ if (!d) {
+ d = new Internal::Data(0, JsonValue::Array);
+ } else if (d->compactionCounter || array.a != d->header->root()) {
+ JsonArray a(array);
+ if (d->compactionCounter)
+ a.compact();
+ else
+ a.detach();
+ d = a.d;
+ d->ref.ref();
+ return;
+ }
+ d->ref.ref();
+}
+
+/*!
+ Returns \c true if the \a other document is equal to this document.
+ */
+bool JsonDocument::operator==(const JsonDocument &other) const
+{
+ if (d == other.d)
+ return true;
+
+ if (!d || !other.d)
+ return false;
+
+ if (d->header->root()->isArray() != other.d->header->root()->isArray())
+ return false;
+
+ if (d->header->root()->isObject())
+ return JsonObject(d, static_cast<Internal::Object *>(d->header->root()))
+ == JsonObject(other.d, static_cast<Internal::Object *>(other.d->header->root()));
+ else
+ return JsonArray(d, static_cast<Internal::Array *>(d->header->root()))
+ == JsonArray(other.d, static_cast<Internal::Array *>(other.d->header->root()));
+}
+
+/*!
+ \fn bool JsonDocument::operator!=(const JsonDocument &other) const
+
+ returns \c true if \a other is not equal to this document
+ */
+
+/*!
+ returns \c true if this document is null.
+
+ Null documents are documents created through the default constructor.
+
+ Documents created from UTF-8 encoded text or the binary format are
+ validated during parsing. If validation fails, the returned document
+ will also be null.
+ */
+bool JsonDocument::isNull() const
+{
+ return (d == 0);
+}
+
+
+static void objectContentToJson(const Object *o, std::string &json, int indent, bool compact);
+static void arrayContentToJson(const Array *a, std::string &json, int indent, bool compact);
+
+static uint8_t hexdig(uint32_t u)
+{
+ return (u < 0xa ? '0' + u : 'a' + u - 0xa);
+}
+
+static std::string escapedString(const std::string &in)
+{
+ std::string ba;
+ ba.reserve(in.length());
+
+ auto src = in.begin();
+ auto end = in.end();
+
+ while (src != end) {
+ uint8_t u = (*src++);
+ if (u < 0x20 || u == 0x22 || u == 0x5c) {
+ ba.push_back('\\');
+ switch (u) {
+ case 0x22:
+ ba.push_back('"');
+ break;
+ case 0x5c:
+ ba.push_back('\\');
+ break;
+ case 0x8:
+ ba.push_back('b');
+ break;
+ case 0xc:
+ ba.push_back('f');
+ break;
+ case 0xa:
+ ba.push_back('n');
+ break;
+ case 0xd:
+ ba.push_back('r');
+ break;
+ case 0x9:
+ ba.push_back('t');
+ break;
+ default:
+ ba.push_back('u');
+ ba.push_back('0');
+ ba.push_back('0');
+ ba.push_back(hexdig(u>>4));
+ ba.push_back(hexdig(u & 0xf));
+ }
+ } else {
+ ba.push_back(u);
+ }
+ }
+
+ return ba;
+}
+
+static void valueToJson(const Base *b, const Value &v, std::string &json, int indent, bool compact)
+{
+ JsonValue::Type type = (JsonValue::Type)(uint32_t)v.type;
+ switch (type) {
+ case JsonValue::Bool:
+ json += v.toBoolean() ? "true" : "false";
+ break;
+ case JsonValue::Double: {
+ const double d = v.toDouble(b);
+ if (std::isfinite(d)) {
+ // +2 to format to ensure the expected precision
+ const int n = std::numeric_limits<double>::digits10 + 2;
+ char buf[30] = {0};
+ sprintf(buf, "%.*g", n, d);
+ // Hack:
+ if (buf[0] == '-' && buf[1] == '0' && buf[2] == '\0')
+ json += "0";
+ else
+ json += buf;
+ } else {
+ json += "null"; // +INF || -INF || NaN (see RFC4627#section2.4)
+ }
+ break;
+ }
+ case JsonValue::String:
+ json += '"';
+ json += escapedString(v.toString(b));
+ json += '"';
+ break;
+ case JsonValue::Array:
+ json += compact ? "[" : "[\n";
+ arrayContentToJson(static_cast<Array *>(v.base(b)), json, indent + (compact ? 0 : 1), compact);
+ json += std::string(4*indent, ' ');
+ json += ']';
+ break;
+ case JsonValue::Object:
+ json += compact ? "{" : "{\n";
+ objectContentToJson(static_cast<Object *>(v.base(b)), json, indent + (compact ? 0 : 1), compact);
+ json += std::string(4*indent, ' ');
+ json += '}';
+ break;
+ case JsonValue::Null:
+ default:
+ json += "null";
+ }
+}
+
+static void arrayContentToJson(const Array *a, std::string &json, int indent, bool compact)
+{
+ if (!a || !a->length)
+ return;
+
+ std::string indentString(4*indent, ' ');
+
+ uint32_t i = 0;
+ while (1) {
+ json += indentString;
+ valueToJson(a, a->at(i), json, indent, compact);
+
+ if (++i == a->length) {
+ if (!compact)
+ json += '\n';
+ break;
+ }
+
+ json += compact ? "," : ",\n";
+ }
+}
+
+static void objectContentToJson(const Object *o, std::string &json, int indent, bool compact)
+{
+ if (!o || !o->length)
+ return;
+
+ std::string indentString(4*indent, ' ');
+
+ uint32_t i = 0;
+ while (1) {
+ Entry *e = o->entryAt(i);
+ json += indentString;
+ json += '"';
+ json += escapedString(e->key());
+ json += compact ? "\":" : "\": ";
+ valueToJson(o, e->value, json, indent, compact);
+
+ if (++i == o->length) {
+ if (!compact)
+ json += '\n';
+ break;
+ }
+
+ json += compact ? "," : ",\n";
+ }
+}
+
+namespace Internal {
+
+void objectToJson(const Object *o, std::string &json, int indent, bool compact)
+{
+ json.reserve(json.size() + (o ? (int)o->size : 16));
+ json += compact ? "{" : "{\n";
+ objectContentToJson(o, json, indent + (compact ? 0 : 1), compact);
+ json += std::string(4*indent, ' ');
+ json += compact ? "}" : "}\n";
+}
+
+void arrayToJson(const Array *a, std::string &json, int indent, bool compact)
+{
+ json.reserve(json.size() + (a ? (int)a->size : 16));
+ json += compact ? "[" : "[\n";
+ arrayContentToJson(a, json, indent + (compact ? 0 : 1), compact);
+ json += std::string(4*indent, ' ');
+ json += compact ? "]" : "]\n";
+}
+
+}
+
+
+
+/*!
+ \class JsonParseError
+ \inmodule QtCore
+ \ingroup json
+ \ingroup shared
+ \reentrant
+ \since 5.0
+
+ \brief The JsonParseError class is used to report errors during JSON parsing.
+
+ \sa {JSON Support in Qt}, {JSON Save Game Example}
+*/
+
+/*!
+ \enum JsonParseError::ParseError
+
+ This enum describes the type of error that occurred during the parsing of a JSON document.
+
+ \value NoError No error occurred
+ \value UnterminatedObject An object is not correctly terminated with a closing curly bracket
+ \value MissingNameSeparator A comma separating different items is missing
+ \value UnterminatedArray The array is not correctly terminated with a closing square bracket
+ \value MissingValueSeparator A colon separating keys from values inside objects is missing
+ \value IllegalValue The value is illegal
+ \value TerminationByNumber The input stream ended while parsing a number
+ \value IllegalNumber The number is not well formed
+ \value IllegalEscapeSequence An illegal escape sequence occurred in the input
+ \value IllegalUTF8String An illegal UTF8 sequence occurred in the input
+ \value UnterminatedString A string wasn't terminated with a quote
+ \value MissingObject An object was expected but couldn't be found
+ \value DeepNesting The JSON document is too deeply nested for the parser to parse it
+ \value DocumentTooLarge The JSON document is too large for the parser to parse it
+ \value GarbageAtEnd The parsed document contains additional garbage characters at the end
+
+*/
+
+/*!
+ \variable JsonParseError::error
+
+ Contains the type of the parse error. Is equal to JsonParseError::NoError if the document
+ was parsed correctly.
+
+ \sa ParseError, errorString()
+*/
+
+
+/*!
+ \variable JsonParseError::offset
+
+ Contains the offset in the input string where the parse error occurred.
+
+ \sa error, errorString()
+*/
+
+using namespace Internal;
+
+Parser::Parser(const char *json, int length)
+ : head(json), json(json), data(0), dataLength(0), current(0), nestingLevel(0), lastError(JsonParseError::NoError)
+{
+ end = json + length;
+}
+
+
+
+/*
+
+begin-array = ws %x5B ws ; [ left square bracket
+
+begin-object = ws %x7B ws ; { left curly bracket
+
+end-array = ws %x5D ws ; ] right square bracket
+
+end-object = ws %x7D ws ; } right curly bracket
+
+name-separator = ws %x3A ws ; : colon
+
+value-separator = ws %x2C ws ; , comma
+
+Insignificant whitespace is allowed before or after any of the six
+structural characters.
+
+ws = *(
+ %x20 / ; Space
+ %x09 / ; Horizontal tab
+ %x0A / ; Line feed or New line
+ %x0D ; Carriage return
+ )
+
+*/
+
+enum {
+ Space = 0x20,
+ Tab = 0x09,
+ LineFeed = 0x0a,
+ Return = 0x0d,
+ BeginArray = 0x5b,
+ BeginObject = 0x7b,
+ EndArray = 0x5d,
+ EndObject = 0x7d,
+ NameSeparator = 0x3a,
+ ValueSeparator = 0x2c,
+ Quote = 0x22
+};
+
+void Parser::eatBOM()
+{
+ // eat UTF-8 byte order mark
+ if (end - json > 3
+ && (unsigned char)json[0] == 0xef
+ && (unsigned char)json[1] == 0xbb
+ && (unsigned char)json[2] == 0xbf)
+ json += 3;
+}
+
+bool Parser::eatSpace()
+{
+ while (json < end) {
+ if (*json > Space)
+ break;
+ if (*json != Space &&
+ *json != Tab &&
+ *json != LineFeed &&
+ *json != Return)
+ break;
+ ++json;
+ }
+ return (json < end);
+}
+
+char Parser::nextToken()
+{
+ if (!eatSpace())
+ return 0;
+ char token = *json++;
+ switch (token) {
+ case BeginArray:
+ case BeginObject:
+ case NameSeparator:
+ case ValueSeparator:
+ case EndArray:
+ case EndObject:
+ eatSpace();
+ case Quote:
+ break;
+ default:
+ token = 0;
+ break;
+ }
+ return token;
+}
+
+/*
+ JSON-text = object / array
+*/
+JsonDocument Parser::parse(JsonParseError *error)
+{
+#ifdef PARSER_DEBUG
+ indent = 0;
+ std::cerr << ">>>>> parser begin";
+#endif
+ // allocate some space
+ dataLength = static_cast<int>(std::max(end - json, std::ptrdiff_t(256)));
+ data = (char *)malloc(dataLength);
+
+ // fill in Header data
+ Header *h = (Header *)data;
+ h->tag = JsonDocument::BinaryFormatTag;
+ h->version = 1u;
+
+ current = sizeof(Header);
+
+ eatBOM();
+ char token = nextToken();
+
+ DEBUG << std::hex << (uint32_t)token;
+ if (token == BeginArray) {
+ if (!parseArray())
+ goto error;
+ } else if (token == BeginObject) {
+ if (!parseObject())
+ goto error;
+ } else {
+ lastError = JsonParseError::IllegalValue;
+ goto error;
+ }
+
+ eatSpace();
+ if (json < end) {
+ lastError = JsonParseError::GarbageAtEnd;
+ goto error;
+ }
+
+ END;
+ {
+ if (error) {
+ error->offset = 0;
+ error->error = JsonParseError::NoError;
+ }
+ Data *d = new Data(data, current);
+ return JsonDocument(d);
+ }
+
+error:
+#ifdef PARSER_DEBUG
+ std::cerr << ">>>>> parser error";
+#endif
+ if (error) {
+ error->offset = static_cast<int>(json - head);
+ error->error = lastError;
+ }
+ free(data);
+ return JsonDocument();
+}
+
+
+void Parser::ParsedObject::insert(uint32_t offset)
+{
+ const Entry *newEntry = reinterpret_cast<const Entry *>(parser->data + objectPosition + offset);
+ size_t min = 0;
+ size_t n = offsets.size();
+ while (n > 0) {
+ size_t half = n >> 1;
+ size_t middle = min + half;
+ if (*entryAt(middle) >= *newEntry) {
+ n = half;
+ } else {
+ min = middle + 1;
+ n -= half + 1;
+ }
+ }
+ if (min < offsets.size() && *entryAt(min) == *newEntry) {
+ offsets[min] = offset;
+ } else {
+ offsets.insert(offsets.begin() + min, offset);
+ }
+}
+
+/*
+ object = begin-object [ member *( value-separator member ) ]
+ end-object
+*/
+
+bool Parser::parseObject()
+{
+ if (++nestingLevel > nestingLimit) {
+ lastError = JsonParseError::DeepNesting;
+ return false;
+ }
+
+ int objectOffset = reserveSpace(sizeof(Object));
+ BEGIN << "parseObject pos=" << objectOffset << current << json;
+
+ ParsedObject parsedObject(this, objectOffset);
+
+ char token = nextToken();
+ while (token == Quote) {
+ int off = current - objectOffset;
+ if (!parseMember(objectOffset))
+ return false;
+ parsedObject.insert(off);
+ token = nextToken();
+ if (token != ValueSeparator)
+ break;
+ token = nextToken();
+ if (token == EndObject) {
+ lastError = JsonParseError::MissingObject;
+ return false;
+ }
+ }
+
+ DEBUG << "end token=" << token;
+ if (token != EndObject) {
+ lastError = JsonParseError::UnterminatedObject;
+ return false;
+ }
+
+ DEBUG << "numEntries" << parsedObject.offsets.size();
+ int table = objectOffset;
+ // finalize the object
+ if (parsedObject.offsets.size()) {
+ int tableSize = static_cast<int>(parsedObject.offsets.size()) * sizeof(uint32_t);
+ table = reserveSpace(tableSize);
+ memcpy(data + table, &*parsedObject.offsets.begin(), tableSize);
+ }
+
+ Object *o = (Object *)(data + objectOffset);
+ o->tableOffset = table - objectOffset;
+ o->size = current - objectOffset;
+ o->is_object = true;
+ o->length = static_cast<int>(parsedObject.offsets.size());
+
+ DEBUG << "current=" << current;
+ END;
+
+ --nestingLevel;
+ return true;
+}
+
+/*
+ member = string name-separator value
+*/
+bool Parser::parseMember(int baseOffset)
+{
+ int entryOffset = reserveSpace(sizeof(Entry));
+ BEGIN << "parseMember pos=" << entryOffset;
+
+ if (!parseString())
+ return false;
+ char token = nextToken();
+ if (token != NameSeparator) {
+ lastError = JsonParseError::MissingNameSeparator;
+ return false;
+ }
+ Value val;
+ if (!parseValue(&val, baseOffset))
+ return false;
+
+ // finalize the entry
+ Entry *e = (Entry *)(data + entryOffset);
+ e->value = val;
+
+ END;
+ return true;
+}
+
+/*
+ array = begin-array [ value *( value-separator value ) ] end-array
+*/
+bool Parser::parseArray()
+{
+ BEGIN << "parseArray";
+
+ if (++nestingLevel > nestingLimit) {
+ lastError = JsonParseError::DeepNesting;
+ return false;
+ }
+
+ int arrayOffset = reserveSpace(sizeof(Array));
+
+ std::vector<Value> values;
+ values.reserve(64);
+
+ if (!eatSpace()) {
+ lastError = JsonParseError::UnterminatedArray;
+ return false;
+ }
+ if (*json == EndArray) {
+ nextToken();
+ } else {
+ while (1) {
+ Value val;
+ if (!parseValue(&val, arrayOffset))
+ return false;
+ values.push_back(val);
+ char token = nextToken();
+ if (token == EndArray)
+ break;
+ else if (token != ValueSeparator) {
+ if (!eatSpace())
+ lastError = JsonParseError::UnterminatedArray;
+ else
+ lastError = JsonParseError::MissingValueSeparator;
+ return false;
+ }
+ }
+ }
+
+ DEBUG << "size =" << values.size();
+ int table = arrayOffset;
+ // finalize the object
+ if (values.size()) {
+ int tableSize = static_cast<int>(values.size() * sizeof(Value));
+ table = reserveSpace(tableSize);
+ memcpy(data + table, values.data(), tableSize);
+ }
+
+ Array *a = (Array *)(data + arrayOffset);
+ a->tableOffset = table - arrayOffset;
+ a->size = current - arrayOffset;
+ a->is_object = false;
+ a->length = static_cast<int>(values.size());
+
+ DEBUG << "current=" << current;
+ END;
+
+ --nestingLevel;
+ return true;
+}
+
+/*
+value = false / null / true / object / array / number / string
+
+*/
+
+bool Parser::parseValue(Value *val, int baseOffset)
+{
+ BEGIN << "parse Value" << json;
+ val->_dummy = 0;
+
+ switch (*json++) {
+ case 'n':
+ if (end - json < 4) {
+ lastError = JsonParseError::IllegalValue;
+ return false;
+ }
+ if (*json++ == 'u' &&
+ *json++ == 'l' &&
+ *json++ == 'l') {
+ val->type = JsonValue::Null;
+ DEBUG << "value: null";
+ END;
+ return true;
+ }
+ lastError = JsonParseError::IllegalValue;
+ return false;
+ case 't':
+ if (end - json < 4) {
+ lastError = JsonParseError::IllegalValue;
+ return false;
+ }
+ if (*json++ == 'r' &&
+ *json++ == 'u' &&
+ *json++ == 'e') {
+ val->type = JsonValue::Bool;
+ val->value = true;
+ DEBUG << "value: true";
+ END;
+ return true;
+ }
+ lastError = JsonParseError::IllegalValue;
+ return false;
+ case 'f':
+ if (end - json < 5) {
+ lastError = JsonParseError::IllegalValue;
+ return false;
+ }
+ if (*json++ == 'a' &&
+ *json++ == 'l' &&
+ *json++ == 's' &&
+ *json++ == 'e') {
+ val->type = JsonValue::Bool;
+ val->value = false;
+ DEBUG << "value: false";
+ END;
+ return true;
+ }
+ lastError = JsonParseError::IllegalValue;
+ return false;
+ case Quote: {
+ val->type = JsonValue::String;
+ if (current - baseOffset >= Value::MaxSize) {
+ lastError = JsonParseError::DocumentTooLarge;
+ return false;
+ }
+ val->value = current - baseOffset;
+ if (!parseString())
+ return false;
+ val->intValue = false;
+ DEBUG << "value: string";
+ END;
+ return true;
+ }
+ case BeginArray:
+ val->type = JsonValue::Array;
+ if (current - baseOffset >= Value::MaxSize) {
+ lastError = JsonParseError::DocumentTooLarge;
+ return false;
+ }
+ val->value = current - baseOffset;
+ if (!parseArray())
+ return false;
+ DEBUG << "value: array";
+ END;
+ return true;
+ case BeginObject:
+ val->type = JsonValue::Object;
+ if (current - baseOffset >= Value::MaxSize) {
+ lastError = JsonParseError::DocumentTooLarge;
+ return false;
+ }
+ val->value = current - baseOffset;
+ if (!parseObject())
+ return false;
+ DEBUG << "value: object";
+ END;
+ return true;
+ case EndArray:
+ lastError = JsonParseError::MissingObject;
+ return false;
+ default:
+ --json;
+ if (!parseNumber(val, baseOffset))
+ return false;
+ DEBUG << "value: number";
+ END;
+ }
+
+ return true;
+}
+
+
+
+
+
+/*
+ number = [ minus ] int [ frac ] [ exp ]
+ decimal-point = %x2E ; .
+ digit1-9 = %x31-39 ; 1-9
+ e = %x65 / %x45 ; e E
+ exp = e [ minus / plus ] 1*DIGIT
+ frac = decimal-point 1*DIGIT
+ int = zero / ( digit1-9 *DIGIT )
+ minus = %x2D ; -
+ plus = %x2B ; +
+ zero = %x30 ; 0
+
+*/
+
+bool Parser::parseNumber(Value *val, int baseOffset)
+{
+ BEGIN << "parseNumber" << json;
+ val->type = JsonValue::Double;
+
+ const char *start = json;
+ bool isInt = true;
+
+ // minus
+ if (json < end && *json == '-')
+ ++json;
+
+ // int = zero / ( digit1-9 *DIGIT )
+ if (json < end && *json == '0') {
+ ++json;
+ } else {
+ while (json < end && *json >= '0' && *json <= '9')
+ ++json;
+ }
+
+ // frac = decimal-point 1*DIGIT
+ if (json < end && *json == '.') {
+ isInt = false;
+ ++json;
+ while (json < end && *json >= '0' && *json <= '9')
+ ++json;
+ }
+
+ // exp = e [ minus / plus ] 1*DIGIT
+ if (json < end && (*json == 'e' || *json == 'E')) {
+ isInt = false;
+ ++json;
+ if (json < end && (*json == '-' || *json == '+'))
+ ++json;
+ while (json < end && *json >= '0' && *json <= '9')
+ ++json;
+ }
+
+ if (json >= end) {
+ lastError = JsonParseError::TerminationByNumber;
+ return false;
+ }
+
+ if (isInt) {
+ char *endptr = const_cast<char *>(json);
+ long long int n = strtoll(start, &endptr, 0);
+ if (endptr != start && n < (1<<25) && n > -(1<<25)) {
+ val->int_value = int(n);
+ val->intValue = true;
+ END;
+ return true;
+ }
+ }
+
+ char *endptr = const_cast<char *>(json);
+ double d = strtod(start, &endptr);
+
+ if (start == endptr || std::isinf(d)) {
+ lastError = JsonParseError::IllegalNumber;
+ return false;
+ }
+
+ int pos = reserveSpace(sizeof(double));
+ memcpy(data + pos, &d, sizeof(double));
+ if (current - baseOffset >= Value::MaxSize) {
+ lastError = JsonParseError::DocumentTooLarge;
+ return false;
+ }
+ val->value = pos - baseOffset;
+ val->intValue = false;
+
+ END;
+ return true;
+}
+
+/*
+
+ string = quotation-mark *char quotation-mark
+
+ char = unescaped /
+ escape (
+ %x22 / ; " quotation mark U+0022
+ %x5C / ; \ reverse solidus U+005C
+ %x2F / ; / solidus U+002F
+ %x62 / ; b backspace U+0008
+ %x66 / ; f form feed U+000C
+ %x6E / ; n line feed U+000A
+ %x72 / ; r carriage return U+000D
+ %x74 / ; t tab U+0009
+ %x75 4HEXDIG ) ; uXXXX U+XXXX
+
+ escape = %x5C ; \
+
+ quotation-mark = %x22 ; "
+
+ unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
+ */
+static bool addHexDigit(char digit, uint32_t *result)
+{
+ *result <<= 4;
+ if (digit >= '0' && digit <= '9')
+ *result |= (digit - '0');
+ else if (digit >= 'a' && digit <= 'f')
+ *result |= (digit - 'a') + 10;
+ else if (digit >= 'A' && digit <= 'F')
+ *result |= (digit - 'A') + 10;
+ else
+ return false;
+ return true;
+}
+
+bool Parser::parseEscapeSequence()
+{
+ DEBUG << "scan escape" << (char)*json;
+ const char escaped = *json++;
+ switch (escaped) {
+ case '"':
+ addChar('"'); break;
+ case '\\':
+ addChar('\\'); break;
+ case '/':
+ addChar('/'); break;
+ case 'b':
+ addChar(0x8); break;
+ case 'f':
+ addChar(0xc); break;
+ case 'n':
+ addChar(0xa); break;
+ case 'r':
+ addChar(0xd); break;
+ case 't':
+ addChar(0x9); break;
+ case 'u': {
+ uint32_t c = 0;
+ if (json > end - 4)
+ return false;
+ for (int i = 0; i < 4; ++i) {
+ if (!addHexDigit(*json, &c))
+ return false;
+ ++json;
+ }
+ if (c < 0x80) {
+ addChar(c);
+ break;
+ }
+ if (c < 0x800) {
+ addChar(192 + c / 64);
+ addChar(128 + c % 64);
+ break;
+ }
+ if (c - 0xd800u < 0x800) {
+ return false;
+ }
+ if (c < 0x10000) {
+ addChar(224 + c / 4096);
+ addChar(128 + c / 64 % 64);
+ addChar(128 + c % 64);
+ break;
+ }
+ if (c < 0x110000) {
+ addChar(240 + c / 262144);
+ addChar(128 + c / 4096 % 64);
+ addChar(128 + c / 64 % 64);
+ addChar(128 + c % 64);
+ break;
+ }
+ return false;
+ }
+ default:
+ // this is not as strict as one could be, but allows for more Json files
+ // to be parsed correctly.
+ addChar(escaped);
+ break;
+ }
+ return true;
+}
+
+bool Parser::parseString()
+{
+ const char *inStart = json;
+
+ // First try quick pass without escapes.
+ if (true) {
+ while (1) {
+ if (json >= end) {
+ ++json;
+ lastError = JsonParseError::UnterminatedString;
+ return false;
+ }
+
+ const char c = *json;
+ if (c == '"') {
+ // write string length and padding.
+ const int len = static_cast<int>(json - inStart);
+ const int pos = reserveSpace(4 + alignedSize(len));
+ toInternal(data + pos, inStart, len);
+ END;
+
+ ++json;
+ return true;
+ }
+
+ if (c == '\\')
+ break;
+ ++json;
+ }
+ }
+
+ // Try again with escapes.
+ const int outStart = reserveSpace(4);
+ json = inStart;
+ while (1) {
+ if (json >= end) {
+ ++json;
+ lastError = JsonParseError::UnterminatedString;
+ return false;
+ }
+
+ if (*json == '"') {
+ ++json;
+ // write string length and padding.
+ *(int *)(data + outStart) = current - outStart - 4;
+ reserveSpace((4 - current) & 3);
+ END;
+ return true;
+ }
+
+ if (*json == '\\') {
+ ++json;
+ if (json >= end || !parseEscapeSequence()) {
+ lastError = JsonParseError::IllegalEscapeSequence;
+ return false;
+ }
+ } else {
+ addChar(*json++);
+ }
+ }
+}
+
+namespace Internal {
+
+static const Base emptyArray = {sizeof(Base), {0}, 0};
+static const Base emptyObject = {sizeof(Base), {0}, 0};
+
+
+void Data::compact()
+{
+ // assert(sizeof(Value) == sizeof(offset));
+
+ if (!compactionCounter)
+ return;
+
+ Base *base = header->root();
+ int reserve = 0;
+ if (base->is_object) {
+ Object *o = static_cast<Object *>(base);
+ for (int i = 0; i < (int)o->length; ++i)
+ reserve += o->entryAt(i)->usedStorage(o);
+ } else {
+ Array *a = static_cast<Array *>(base);
+ for (int i = 0; i < (int)a->length; ++i)
+ reserve += (*a)[i].usedStorage(a);
+ }
+
+ int size = sizeof(Base) + reserve + base->length*sizeof(offset);
+ int alloc = sizeof(Header) + size;
+ Header *h = (Header *) malloc(alloc);
+ h->tag = JsonDocument::BinaryFormatTag;
+ h->version = 1;
+ Base *b = h->root();
+ b->size = size;
+ b->is_object = header->root()->is_object;
+ b->length = base->length;
+ b->tableOffset = reserve + sizeof(Array);
+
+ int offset = sizeof(Base);
+ if (b->is_object) {
+ Object *o = static_cast<Object *>(base);
+ Object *no = static_cast<Object *>(b);
+
+ for (int i = 0; i < (int)o->length; ++i) {
+ no->table()[i] = offset;
+
+ const Entry *e = o->entryAt(i);
+ Entry *ne = no->entryAt(i);
+ int s = e->size();
+ memcpy(ne, e, s);
+ offset += s;
+ int dataSize = e->value.usedStorage(o);
+ if (dataSize) {
+ memcpy((char *)no + offset, e->value.data(o), dataSize);
+ ne->value.value = offset;
+ offset += dataSize;
+ }
+ }
+ } else {
+ Array *a = static_cast<Array *>(base);
+ Array *na = static_cast<Array *>(b);
+
+ for (int i = 0; i < (int)a->length; ++i) {
+ const Value &v = (*a)[i];
+ Value &nv = (*na)[i];
+ nv = v;
+ int dataSize = v.usedStorage(a);
+ if (dataSize) {
+ memcpy((char *)na + offset, v.data(a), dataSize);
+ nv.value = offset;
+ offset += dataSize;
+ }
+ }
+ }
+ // assert(offset == (int)b->tableOffset);
+
+ free(header);
+ header = h;
+ this->alloc = alloc;
+ compactionCounter = 0;
+}
+
+bool Data::valid() const
+{
+ if (header->tag != JsonDocument::BinaryFormatTag || header->version != 1u)
+ return false;
+
+ bool res = false;
+ if (header->root()->is_object)
+ res = static_cast<Object *>(header->root())->isValid();
+ else
+ res = static_cast<Array *>(header->root())->isValid();
+
+ return res;
+}
+
+
+int Base::reserveSpace(uint32_t dataSize, int posInTable, uint32_t numItems, bool replace)
+{
+ // assert(posInTable >= 0 && posInTable <= (int)length);
+ if (size + dataSize >= Value::MaxSize) {
+ fprintf(stderr, "Json: Document too large to store in data structure %d %d %d\n", (uint32_t)size, dataSize, Value::MaxSize);
+ return 0;
+ }
+
+ offset off = tableOffset;
+ // move table to new position
+ if (replace) {
+ memmove((char *)(table()) + dataSize, table(), length*sizeof(offset));
+ } else {
+ memmove((char *)(table() + posInTable + numItems) + dataSize, table() + posInTable, (length - posInTable)*sizeof(offset));
+ memmove((char *)(table()) + dataSize, table(), posInTable*sizeof(offset));
+ }
+ tableOffset += dataSize;
+ for (int i = 0; i < (int)numItems; ++i)
+ table()[posInTable + i] = off;
+ size += dataSize;
+ if (!replace) {
+ length += numItems;
+ size += numItems * sizeof(offset);
+ }
+ return off;
+}
+
+void Base::removeItems(int pos, int numItems)
+{
+ // assert(pos >= 0 && pos <= (int)length);
+ if (pos + numItems < (int)length)
+ memmove(table() + pos, table() + pos + numItems, (length - pos - numItems)*sizeof(offset));
+ length -= numItems;
+}
+
+int Object::indexOf(const std::string &key, bool *exists)
+{
+ int min = 0;
+ int n = length;
+ while (n > 0) {
+ int half = n >> 1;
+ int middle = min + half;
+ if (*entryAt(middle) >= key) {
+ n = half;
+ } else {
+ min = middle + 1;
+ n -= half + 1;
+ }
+ }
+ if (min < (int)length && *entryAt(min) == key) {
+ *exists = true;
+ return min;
+ }
+ *exists = false;
+ return min;
+}
+
+bool Object::isValid() const
+{
+ if (tableOffset + length*sizeof(offset) > size)
+ return false;
+
+ std::string lastKey;
+ for (uint32_t i = 0; i < length; ++i) {
+ offset entryOffset = table()[i];
+ if (entryOffset + sizeof(Entry) >= tableOffset)
+ return false;
+ Entry *e = entryAt(i);
+ int s = e->size();
+ if (table()[i] + s > tableOffset)
+ return false;
+ std::string key = e->key();
+ if (key < lastKey)
+ return false;
+ if (!e->value.isValid(this))
+ return false;
+ lastKey = key;
+ }
+ return true;
+}
+
+bool Array::isValid() const
+{
+ if (tableOffset + length*sizeof(offset) > size)
+ return false;
+
+ for (uint32_t i = 0; i < length; ++i) {
+ if (!at(i).isValid(this))
+ return false;
+ }
+ return true;
+}
+
+
+bool Entry::operator==(const std::string &key) const
+{
+ return shallowKey() == key;
+}
+
+bool Entry::operator==(const Entry &other) const
+{
+ return shallowKey() == other.shallowKey();
+}
+
+bool Entry::operator>=(const Entry &other) const
+{
+ return shallowKey() >= other.shallowKey();
+}
+
+
+int Value::usedStorage(const Base *b) const
+{
+ int s = 0;
+ switch (type) {
+ case JsonValue::Double:
+ if (intValue)
+ break;
+ s = sizeof(double);
+ break;
+ case JsonValue::String: {
+ char *d = data(b);
+ s = sizeof(int) + (*(int *)d);
+ break;
+ }
+ case JsonValue::Array:
+ case JsonValue::Object:
+ s = base(b)->size;
+ break;
+ case JsonValue::Null:
+ case JsonValue::Bool:
+ default:
+ break;
+ }
+ return alignedSize(s);
+}
+
+bool Value::isValid(const Base *b) const
+{
+ int offset = 0;
+ switch (type) {
+ case JsonValue::Double:
+ if (intValue)
+ break;
+ // fall through
+ case JsonValue::String:
+ case JsonValue::Array:
+ case JsonValue::Object:
+ offset = value;
+ break;
+ case JsonValue::Null:
+ case JsonValue::Bool:
+ default:
+ break;
+ }
+
+ if (!offset)
+ return true;
+ if (offset + sizeof(uint32_t) > b->tableOffset)
+ return false;
+
+ int s = usedStorage(b);
+ if (!s)
+ return true;
+ if (s < 0 || offset + s > (int)b->tableOffset)
+ return false;
+ if (type == JsonValue::Array)
+ return static_cast<Array *>(base(b))->isValid();
+ if (type == JsonValue::Object)
+ return static_cast<Object *>(base(b))->isValid();
+ return true;
+}
+
+/*!
+ \internal
+ */
+int Value::requiredStorage(JsonValue &v, bool *compressed)
+{
+ *compressed = false;
+ switch (v.t) {
+ case JsonValue::Double:
+ if (Internal::compressedNumber(v.dbl) != INT_MAX) {
+ *compressed = true;
+ return 0;
+ }
+ return sizeof(double);
+ case JsonValue::String: {
+ std::string s = v.toString().data();
+ *compressed = false;
+ return Internal::qStringSize(s);
+ }
+ case JsonValue::Array:
+ case JsonValue::Object:
+ if (v.d && v.d->compactionCounter) {
+ v.detach();
+ v.d->compact();
+ v.base = static_cast<Internal::Base *>(v.d->header->root());
+ }
+ return v.base ? v.base->size : sizeof(Internal::Base);
+ case JsonValue::Undefined:
+ case JsonValue::Null:
+ case JsonValue::Bool:
+ break;
+ }
+ return 0;
+}
+
+/*!
+ \internal
+ */
+uint32_t Value::valueToStore(const JsonValue &v, uint32_t offset)
+{
+ switch (v.t) {
+ case JsonValue::Undefined:
+ case JsonValue::Null:
+ break;
+ case JsonValue::Bool:
+ return v.b;
+ case JsonValue::Double: {
+ int c = Internal::compressedNumber(v.dbl);
+ if (c != INT_MAX)
+ return c;
+ }
+ // fall through
+ case JsonValue::String:
+ case JsonValue::Array:
+ case JsonValue::Object:
+ return offset;
+ }
+ return 0;
+}
+
+/*!
+ \internal
+ */
+
+void Value::copyData(const JsonValue &v, char *dest, bool compressed)
+{
+ switch (v.t) {
+ case JsonValue::Double:
+ if (!compressed)
+ memcpy(dest, &v.ui, 8);
+ break;
+ case JsonValue::String: {
+ std::string str = v.toString();
+ Internal::copyString(dest, str);
+ break;
+ }
+ case JsonValue::Array:
+ case JsonValue::Object: {
+ const Internal::Base *b = v.base;
+ if (!b)
+ b = (v.t == JsonValue::Array ? &emptyArray : &emptyObject);
+ memcpy(dest, b, b->size);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+} // namespace Internal
+} // namespace Json
diff --git a/src/shared/json/json.h b/src/shared/json/json.h
new file mode 100644
index 000000000..3c673fc79
--- /dev/null
+++ b/src/shared/json/json.h
@@ -0,0 +1,589 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef JSON_H
+#define JSON_H
+
+#include <cstdint>
+#include <initializer_list>
+#include <string>
+#include <vector>
+
+namespace Json {
+
+class JsonArray;
+class JsonObject;
+
+namespace Internal {
+class Data;
+class Base;
+class Object;
+class Header;
+class Array;
+class Value;
+class Entry;
+class SharedString;
+class Parser;
+}
+
+class JsonValue
+{
+public:
+ enum Type {
+ Null = 0x0,
+ Bool = 0x1,
+ Double = 0x2,
+ String = 0x3,
+ Array = 0x4,
+ Object = 0x5,
+ Undefined = 0x80
+ };
+
+ JsonValue(Type = Null);
+ JsonValue(bool b);
+ JsonValue(double n);
+ JsonValue(int n);
+ JsonValue(int64_t n);
+ JsonValue(const std::string &s);
+ JsonValue(const char *s);
+ JsonValue(const JsonArray &a);
+ JsonValue(const JsonObject &o);
+
+ ~JsonValue();
+
+ JsonValue(const JsonValue &other);
+ JsonValue &operator =(const JsonValue &other);
+
+ Type type() const { return t; }
+ bool isNull() const { return t == Null; }
+ bool isBool() const { return t == Bool; }
+ bool isDouble() const { return t == Double; }
+ bool isString() const { return t == String; }
+ bool isArray() const { return t == Array; }
+ bool isObject() const { return t == Object; }
+ bool isUndefined() const { return t == Undefined; }
+
+ bool toBool(bool defaultValue = false) const;
+ int toInt(int defaultValue = 0) const;
+ double toDouble(double defaultValue = 0) const;
+ std::string toString(const std::string &defaultValue = std::string()) const;
+ JsonArray toArray() const;
+ JsonArray toArray(const JsonArray &defaultValue) const;
+ JsonObject toObject() const;
+ JsonObject toObject(const JsonObject &defaultValue) const;
+
+ bool operator==(const JsonValue &other) const;
+ bool operator!=(const JsonValue &other) const;
+
+private:
+ // avoid implicit conversions from char * to bool
+ JsonValue(const void *) : t(Null) {}
+ friend class Internal::Value;
+ friend class JsonArray;
+ friend class JsonObject;
+
+ JsonValue(Internal::Data *d, Internal::Base *b, const Internal::Value& v);
+
+ void detach();
+
+ union {
+ uint64_t ui;
+ bool b;
+ double dbl;
+ Internal::SharedString *stringData;
+ Internal::Base *base;
+ };
+ Internal::Data *d; // needed for Objects and Arrays
+ Type t;
+};
+
+class JsonValueRef
+{
+public:
+ JsonValueRef(JsonArray *array, int idx)
+ : a(array), is_object(false), index(idx) {}
+ JsonValueRef(JsonObject *object, int idx)
+ : o(object), is_object(true), index(idx) {}
+
+ operator JsonValue() const { return toValue(); }
+ JsonValueRef &operator=(const JsonValue &val);
+ JsonValueRef &operator=(const JsonValueRef &val);
+
+ JsonValue::Type type() const { return toValue().type(); }
+ bool isNull() const { return type() == JsonValue::Null; }
+ bool isBool() const { return type() == JsonValue::Bool; }
+ bool isDouble() const { return type() == JsonValue::Double; }
+ bool isString() const { return type() == JsonValue::String; }
+ bool isArray() const { return type() == JsonValue::Array; }
+ bool isObject() const { return type() == JsonValue::Object; }
+ bool isUndefined() const { return type() == JsonValue::Undefined; }
+
+ std::string toString() const { return toValue().toString(); }
+ JsonArray toArray() const;
+ JsonObject toObject() const;
+
+ bool toBool(bool defaultValue = false) const { return toValue().toBool(defaultValue); }
+ int toInt(int defaultValue = 0) const { return toValue().toInt(defaultValue); }
+ double toDouble(double defaultValue = 0) const { return toValue().toDouble(defaultValue); }
+ std::string toString(const std::string &defaultValue) const { return toValue().toString(defaultValue); }
+
+ bool operator==(const JsonValue &other) const { return toValue() == other; }
+ bool operator!=(const JsonValue &other) const { return toValue() != other; }
+
+private:
+ JsonValue toValue() const;
+
+ union {
+ JsonArray *a;
+ JsonObject *o;
+ };
+ uint32_t is_object : 1;
+ uint32_t index : 31;
+};
+
+class JsonValuePtr
+{
+ JsonValue value;
+public:
+ explicit JsonValuePtr(const JsonValue& val)
+ : value(val) {}
+
+ JsonValue& operator*() { return value; }
+ JsonValue* operator->() { return &value; }
+};
+
+class JsonValueRefPtr
+{
+ JsonValueRef valueRef;
+public:
+ JsonValueRefPtr(JsonArray *array, int idx)
+ : valueRef(array, idx) {}
+ JsonValueRefPtr(JsonObject *object, int idx)
+ : valueRef(object, idx) {}
+
+ JsonValueRef& operator*() { return valueRef; }
+ JsonValueRef* operator->() { return &valueRef; }
+};
+
+
+
+class JsonArray
+{
+public:
+ JsonArray();
+ JsonArray(std::initializer_list<JsonValue> args);
+
+ ~JsonArray();
+
+ JsonArray(const JsonArray &other);
+ JsonArray &operator=(const JsonArray &other);
+
+ int size() const;
+ int count() const { return size(); }
+
+ bool isEmpty() const;
+ JsonValue at(int i) const;
+ JsonValue first() const;
+ JsonValue last() const;
+
+ void prepend(const JsonValue &value);
+ void append(const JsonValue &value);
+ void removeAt(int i);
+ JsonValue takeAt(int i);
+ void removeFirst() { removeAt(0); }
+ void removeLast() { removeAt(size() - 1); }
+
+ void insert(int i, const JsonValue &value);
+ void replace(int i, const JsonValue &value);
+
+ bool contains(const JsonValue &element) const;
+ JsonValueRef operator[](int i);
+ JsonValue operator[](int i) const;
+
+ bool operator==(const JsonArray &other) const;
+ bool operator!=(const JsonArray &other) const;
+
+ class const_iterator;
+
+ class iterator {
+ public:
+ JsonArray *a;
+ int i;
+ typedef std::random_access_iterator_tag iterator_category;
+ typedef int difference_type;
+ typedef JsonValue value_type;
+ typedef JsonValueRef reference;
+ typedef JsonValueRefPtr pointer;
+
+ iterator() : a(nullptr), i(0) { }
+ explicit iterator(JsonArray *array, int index) : a(array), i(index) { }
+
+ JsonValueRef operator*() const { return JsonValueRef(a, i); }
+ JsonValueRefPtr operator->() const { return JsonValueRefPtr(a, i); }
+ JsonValueRef operator[](int j) const { return JsonValueRef(a, i + j); }
+
+ bool operator==(const iterator &o) const { return i == o.i; }
+ bool operator!=(const iterator &o) const { return i != o.i; }
+ bool operator<(const iterator& other) const { return i < other.i; }
+ bool operator<=(const iterator& other) const { return i <= other.i; }
+ bool operator>(const iterator& other) const { return i > other.i; }
+ bool operator>=(const iterator& other) const { return i >= other.i; }
+ bool operator==(const const_iterator &o) const { return i == o.i; }
+ bool operator!=(const const_iterator &o) const { return i != o.i; }
+ bool operator<(const const_iterator& other) const { return i < other.i; }
+ bool operator<=(const const_iterator& other) const { return i <= other.i; }
+ bool operator>(const const_iterator& other) const { return i > other.i; }
+ bool operator>=(const const_iterator& other) const { return i >= other.i; }
+ iterator &operator++() { ++i; return *this; }
+ iterator operator++(int) { iterator n = *this; ++i; return n; }
+ iterator &operator--() { i--; return *this; }
+ iterator operator--(int) { iterator n = *this; i--; return n; }
+ iterator &operator+=(int j) { i+=j; return *this; }
+ iterator &operator-=(int j) { i-=j; return *this; }
+ iterator operator+(int j) const { return iterator(a, i+j); }
+ iterator operator-(int j) const { return iterator(a, i-j); }
+ int operator-(iterator j) const { return i - j.i; }
+ };
+ friend class iterator;
+
+ class const_iterator {
+ public:
+ const JsonArray *a;
+ int i;
+ typedef std::random_access_iterator_tag iterator_category;
+ typedef std::ptrdiff_t difference_type;
+ typedef JsonValue value_type;
+ typedef JsonValue reference;
+ typedef JsonValuePtr pointer;
+
+ const_iterator() : a(nullptr), i(0) { }
+ explicit const_iterator(const JsonArray *array, int index) : a(array), i(index) { }
+ const_iterator(const iterator &o) : a(o.a), i(o.i) {}
+
+ JsonValue operator*() const { return a->at(i); }
+ JsonValuePtr operator->() const { return JsonValuePtr(a->at(i)); }
+ JsonValue operator[](int j) const { return a->at(i+j); }
+ bool operator==(const const_iterator &o) const { return i == o.i; }
+ bool operator!=(const const_iterator &o) const { return i != o.i; }
+ bool operator<(const const_iterator& other) const { return i < other.i; }
+ bool operator<=(const const_iterator& other) const { return i <= other.i; }
+ bool operator>(const const_iterator& other) const { return i > other.i; }
+ bool operator>=(const const_iterator& other) const { return i >= other.i; }
+ const_iterator &operator++() { ++i; return *this; }
+ const_iterator operator++(int) { const_iterator n = *this; ++i; return n; }
+ const_iterator &operator--() { i--; return *this; }
+ const_iterator operator--(int) { const_iterator n = *this; i--; return n; }
+ const_iterator &operator+=(int j) { i+=j; return *this; }
+ const_iterator &operator-=(int j) { i-=j; return *this; }
+ const_iterator operator+(int j) const { return const_iterator(a, i+j); }
+ const_iterator operator-(int j) const { return const_iterator(a, i-j); }
+ int operator-(const_iterator j) const { return i - j.i; }
+ };
+ friend class const_iterator;
+
+ // stl style
+ iterator begin() { detach(); return iterator(this, 0); }
+ const_iterator begin() const { return const_iterator(this, 0); }
+ const_iterator constBegin() const { return const_iterator(this, 0); }
+ iterator end() { detach(); return iterator(this, size()); }
+ const_iterator end() const { return const_iterator(this, size()); }
+ const_iterator constEnd() const { return const_iterator(this, size()); }
+ iterator insert(iterator before, const JsonValue &value) { insert(before.i, value); return before; }
+ iterator erase(iterator it) { removeAt(it.i); return it; }
+
+ void push_back(const JsonValue &t) { append(t); }
+ void push_front(const JsonValue &t) { prepend(t); }
+ void pop_front() { removeFirst(); }
+ void pop_back() { removeLast(); }
+ bool empty() const { return isEmpty(); }
+ typedef int size_type;
+ typedef JsonValue value_type;
+ typedef value_type *pointer;
+ typedef const value_type *const_pointer;
+ typedef JsonValueRef reference;
+ typedef JsonValue const_reference;
+ typedef int difference_type;
+
+private:
+ friend class Internal::Data;
+ friend class JsonValue;
+ friend class JsonDocument;
+
+ JsonArray(Internal::Data *data, Internal::Array *array);
+ void compact();
+ void detach(uint32_t reserve = 0);
+
+ Internal::Data *d;
+ Internal::Array *a;
+};
+
+
+class JsonObject
+{
+public:
+ JsonObject();
+ JsonObject(std::initializer_list<std::pair<std::string, JsonValue> > args);
+ ~JsonObject();
+
+ JsonObject(const JsonObject &other);
+ JsonObject &operator =(const JsonObject &other);
+
+ typedef std::vector<std::string> Keys;
+ Keys keys() const;
+ int size() const;
+ int count() const { return size(); }
+ int length() const { return size(); }
+ bool isEmpty() const;
+
+ JsonValue value(const std::string &key) const;
+ JsonValue operator[] (const std::string &key) const;
+ JsonValueRef operator[] (const std::string &key);
+
+ void remove(const std::string &key);
+ JsonValue take(const std::string &key);
+ bool contains(const std::string &key) const;
+
+ bool operator==(const JsonObject &other) const;
+ bool operator!=(const JsonObject &other) const;
+
+ class const_iterator;
+
+ class iterator
+ {
+ friend class const_iterator;
+ friend class JsonObject;
+ JsonObject *o;
+ int i;
+
+ public:
+ typedef std::bidirectional_iterator_tag iterator_category;
+ typedef int difference_type;
+ typedef JsonValue value_type;
+ typedef JsonValueRef reference;
+
+ iterator() : o(nullptr), i(0) {}
+ iterator(JsonObject *obj, int index) : o(obj), i(index) {}
+
+ std::string key() const { return o->keyAt(i); }
+ JsonValueRef value() const { return JsonValueRef(o, i); }
+ JsonValueRef operator*() const { return JsonValueRef(o, i); }
+ JsonValueRefPtr operator->() const { return JsonValueRefPtr(o, i); }
+ bool operator==(const iterator &other) const { return i == other.i; }
+ bool operator!=(const iterator &other) const { return i != other.i; }
+
+ iterator &operator++() { ++i; return *this; }
+ iterator operator++(int) { iterator r = *this; ++i; return r; }
+ iterator &operator--() { --i; return *this; }
+ iterator operator--(int) { iterator r = *this; --i; return r; }
+ iterator operator+(int j) const
+ { iterator r = *this; r.i += j; return r; }
+ iterator operator-(int j) const { return operator+(-j); }
+ iterator &operator+=(int j) { i += j; return *this; }
+ iterator &operator-=(int j) { i -= j; return *this; }
+
+ public:
+ bool operator==(const const_iterator &other) const { return i == other.i; }
+ bool operator!=(const const_iterator &other) const { return i != other.i; }
+ };
+ friend class iterator;
+
+ class const_iterator
+ {
+ friend class iterator;
+ const JsonObject *o;
+ int i;
+
+ public:
+ typedef std::bidirectional_iterator_tag iterator_category;
+ typedef int difference_type;
+ typedef JsonValue value_type;
+ typedef JsonValue reference;
+
+ const_iterator() : o(nullptr), i(0) {}
+ const_iterator(const JsonObject *obj, int index)
+ : o(obj), i(index) {}
+ const_iterator(const iterator &other)
+ : o(other.o), i(other.i) {}
+
+ std::string key() const { return o->keyAt(i); }
+ JsonValue value() const { return o->valueAt(i); }
+ JsonValue operator*() const { return o->valueAt(i); }
+ JsonValuePtr operator->() const { return JsonValuePtr(o->valueAt(i)); }
+ bool operator==(const const_iterator &other) const { return i == other.i; }
+ bool operator!=(const const_iterator &other) const { return i != other.i; }
+
+ const_iterator &operator++() { ++i; return *this; }
+ const_iterator operator++(int) { const_iterator r = *this; ++i; return r; }
+ const_iterator &operator--() { --i; return *this; }
+ const_iterator operator--(int) { const_iterator r = *this; --i; return r; }
+ const_iterator operator+(int j) const
+ { const_iterator r = *this; r.i += j; return r; }
+ const_iterator operator-(int j) const { return operator+(-j); }
+ const_iterator &operator+=(int j) { i += j; return *this; }
+ const_iterator &operator-=(int j) { i -= j; return *this; }
+
+ bool operator==(const iterator &other) const { return i == other.i; }
+ bool operator!=(const iterator &other) const { return i != other.i; }
+ };
+ friend class const_iterator;
+
+ // STL style
+ iterator begin() { detach(); return iterator(this, 0); }
+ const_iterator begin() const { return const_iterator(this, 0); }
+ const_iterator constBegin() const { return const_iterator(this, 0); }
+ iterator end() { detach(); return iterator(this, size()); }
+ const_iterator end() const { return const_iterator(this, size()); }
+ const_iterator constEnd() const { return const_iterator(this, size()); }
+ iterator erase(iterator it);
+
+ // more Qt
+ iterator find(const std::string &key);
+ const_iterator find(const std::string &key) const { return constFind(key); }
+ const_iterator constFind(const std::string &key) const;
+ iterator insert(const std::string &key, const JsonValue &value);
+
+ // STL compatibility
+ typedef JsonValue mapped_type;
+ typedef std::string key_type;
+ typedef int size_type;
+
+ bool empty() const { return isEmpty(); }
+
+private:
+ friend class Internal::Data;
+ friend class JsonValue;
+ friend class JsonDocument;
+ friend class JsonValueRef;
+
+ JsonObject(Internal::Data *data, Internal::Object *object);
+ void detach(uint32_t reserve = 0);
+ void compact();
+
+ std::string keyAt(int i) const;
+ JsonValue valueAt(int i) const;
+ void setValueAt(int i, const JsonValue &val);
+
+ Internal::Data *d;
+ Internal::Object *o;
+};
+
+struct JsonParseError
+{
+ enum ParseError {
+ NoError = 0,
+ UnterminatedObject,
+ MissingNameSeparator,
+ UnterminatedArray,
+ MissingValueSeparator,
+ IllegalValue,
+ TerminationByNumber,
+ IllegalNumber,
+ IllegalEscapeSequence,
+ IllegalUTF8String,
+ UnterminatedString,
+ MissingObject,
+ DeepNesting,
+ DocumentTooLarge,
+ GarbageAtEnd
+ };
+
+ int offset;
+ ParseError error;
+};
+
+class JsonDocument
+{
+public:
+ static const uint32_t BinaryFormatTag = ('q') | ('b' << 8) | ('j' << 16) | ('s' << 24);
+ JsonDocument();
+ explicit JsonDocument(const JsonObject &object);
+ explicit JsonDocument(const JsonArray &array);
+ ~JsonDocument();
+
+ JsonDocument(const JsonDocument &other);
+ JsonDocument &operator =(const JsonDocument &other);
+
+ enum DataValidation {
+ Validate,
+ BypassValidation
+ };
+
+ static JsonDocument fromRawData(const char *data, int size, DataValidation validation = Validate);
+ const char *rawData(int *size) const;
+
+ static JsonDocument fromBinaryData(const std::string &data, DataValidation validation = Validate);
+ std::string toBinaryData() const;
+
+ enum JsonFormat {
+ Indented,
+ Compact
+ };
+
+ static JsonDocument fromJson(const std::string &json, JsonParseError *error = nullptr);
+
+ std::string toJson(JsonFormat format = Indented) const;
+
+ bool isEmpty() const;
+ bool isArray() const;
+ bool isObject() const;
+
+ JsonObject object() const;
+ JsonArray array() const;
+
+ void setObject(const JsonObject &object);
+ void setArray(const JsonArray &array);
+
+ bool operator==(const JsonDocument &other) const;
+ bool operator!=(const JsonDocument &other) const { return !(*this == other); }
+
+ bool isNull() const;
+
+private:
+ friend class JsonValue;
+ friend class Internal::Data;
+ friend class Internal::Parser;
+
+ JsonDocument(Internal::Data *data);
+
+ Internal::Data *d;
+};
+
+} // namespace Json
+
+#endif // JSON_H
diff --git a/src/shared/json/json.pri b/src/shared/json/json.pri
new file mode 100644
index 000000000..1b44027c6
--- /dev/null
+++ b/src/shared/json/json.pri
@@ -0,0 +1,3 @@
+INCLUDEPATH += $$PWD
+HEADERS += $$PWD/json.h
+SOURCES += $$PWD/json.cpp
diff --git a/src/shared/json/json.qbs b/src/shared/json/json.qbs
new file mode 100644
index 000000000..ce2653394
--- /dev/null
+++ b/src/shared/json/json.qbs
@@ -0,0 +1,16 @@
+import qbs
+
+StaticLibrary {
+ name: "qbsjson"
+ Depends { name: "cpp" }
+ cpp.cxxLanguageVersion: "c++11"
+ cpp.minimumMacosVersion: "10.7"
+ files: [
+ "json.cpp",
+ "json.h",
+ ]
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [product.sourceDirectory]
+ }
+}
diff --git a/src/src.qbs b/src/src.qbs
index 411238d1f..97c271850 100644
--- a/src/src.qbs
+++ b/src/src.qbs
@@ -6,6 +6,7 @@ Project {
"lib/libs.qbs",
"libexec/libexec.qbs",
"packages/packages.qbs",
- "plugins/plugins.qbs"
+ "plugins/plugins.qbs",
+ "shared/json/json.qbs"
]
}
diff --git a/tests/auto/api/testdata/local-profiles/local-profiles.qbs b/tests/auto/api/testdata/local-profiles/local-profiles.qbs
new file mode 100644
index 000000000..092544ea9
--- /dev/null
+++ b/tests/auto/api/testdata/local-profiles/local-profiles.qbs
@@ -0,0 +1,45 @@
+import qbs
+
+Project {
+ property string windowsProfile: "windowsProfile"
+ property bool enableProfiles
+ property stringList mingwToolchain: ["mingw", "gcc"]
+ Profile {
+ name: windowsProfile
+ qbs.targetOS: ["windows"]
+ }
+
+ Profile {
+ name: "mingwProfile"
+ condition: enableProfiles
+ baseProfile: project.windowsProfile
+ qbs.toolchain: project.mingwToolchain
+ }
+
+ Application {
+ name: "app"
+ Depends { name: "cpp"; required: false }
+ aggregate: false
+ multiplexByQbsProperties: ["buildVariants", "profiles"]
+ qbs.buildVariants: ["debug", "release"]
+ qbs.profiles: ["mingwProfile"]
+ }
+ DynamicLibrary {
+ name: "lib"
+
+ Depends { name: "cpp"; required: false }
+
+ property stringList clangToolchain: ["clang", "llvm", "gcc"]
+ property string clangProfileName: "clangProfile"
+
+ Profile {
+ name: product.clangProfileName
+ condition: project.enableProfiles
+ qbs.targetOS: ["linux", "unix"]
+ qbs.toolchain: product.clangToolchain
+ }
+
+ multiplexByQbsProperties: ["profiles"]
+ qbs.profiles: ["mingwProfile", "clangProfile"]
+ }
+}
diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp
index 71d8117c0..528714704 100644
--- a/tests/auto/api/tst_api.cpp
+++ b/tests/auto/api/tst_api.cpp
@@ -1489,6 +1489,126 @@ void TestApi::listBuildSystemFiles()
+ "/subproject2/subproject3/subproject3.qbs"));
}
+void TestApi::localProfiles()
+{
+ QFETCH(bool, enableProfiles);
+ qbs::SetupProjectParameters setupParams
+ = defaultSetupParameters("local-profiles/local-profiles.qbs");
+ setupParams.setOverriddenValues(
+ {std::make_pair(QString("project.enableProfiles"), enableProfiles)});
+ QScopedPointer<qbs::SetupProjectJob> job(qbs::Project().setupProject(setupParams,
+ m_logSink, 0));
+ QString taskDescriptions;
+ const auto taskDescHandler = [&taskDescriptions](const QString &desc, int, qbs::AbstractJob *) {
+ taskDescriptions += '\n' + desc;
+ };
+ connect(job.data(), &qbs::AbstractJob::taskStarted, taskDescHandler);
+ waitForFinished(job.data());
+ const QString error = job->error().toString();
+ QVERIFY2(job->error().hasError() == !enableProfiles, qPrintable(error));
+ if (!enableProfiles) {
+ QVERIFY2(error.contains("does not exist"), qPrintable(error));
+ return;
+ }
+ QVERIFY2(taskDescriptions.contains("Resolving"), qPrintable(taskDescriptions));
+
+ qbs::ProjectData project = job->project().projectData();
+ QList<qbs::ProductData> products = project.allProducts();
+ QCOMPARE(products.count(), 4);
+ qbs::ProductData libMingw;
+ qbs::ProductData libClang;
+ qbs::ProductData appDebug;
+ qbs::ProductData appRelease;
+ for (const qbs::ProductData &p : qAsConst(products)) {
+ if (p.name() == "lib") {
+ if (p.profile() == "mingwProfile")
+ libMingw = p;
+ else if (p.profile() == "clangProfile")
+ libClang = p;
+ } else if (p.name() == "app") {
+ const QString buildVariant
+ = p.moduleProperties().getModuleProperty("qbs", "buildVariant").toString();
+ if (buildVariant == "debug")
+ appDebug = p;
+ else if (buildVariant == "release")
+ appRelease = p;
+
+ }
+ }
+ QVERIFY(libMingw.isValid());
+ QVERIFY((libClang.isValid()));
+ QVERIFY(appDebug.isValid());
+ QVERIFY(appRelease.isValid());
+ QCOMPARE(appDebug.profile(), QLatin1String("mingwProfile"));
+ QCOMPARE(appRelease.profile(), QLatin1String("mingwProfile"));
+
+ qbs::PropertyMap moduleProps = libMingw.moduleProperties();
+ QCOMPARE(moduleProps.getModuleProperty("qbs", "targetOS").toStringList(),
+ QStringList({"windows"}));
+ QCOMPARE(moduleProps.getModuleProperty("qbs", "toolchain").toStringList(),
+ QStringList({"mingw", "gcc"}));
+ if (moduleProps.getModuleProperty("cpp", "present").toBool()) {
+ QCOMPARE(moduleProps.getModuleProperty("cpp", "cxxCompilerName").toString(),
+ QString("g++"));
+ }
+ moduleProps = libClang.moduleProperties();
+ QCOMPARE(moduleProps.getModuleProperty("qbs", "targetOS").toStringList(),
+ QStringList({"linux", "unix"}));
+ QCOMPARE(moduleProps.getModuleProperty("qbs", "toolchain").toStringList(),
+ QStringList({"clang", "llvm", "gcc"}));
+ if (moduleProps.getModuleProperty("cpp", "present").toBool()) {
+ QCOMPARE(moduleProps.getModuleProperty("cpp", "cxxCompilerName").toString(),
+ QString("clang++"));
+ }
+ moduleProps = appDebug.moduleProperties();
+ if (moduleProps.getModuleProperty("cpp", "present").toBool())
+ QCOMPARE(moduleProps.getModuleProperty("cpp", "optimization").toString(), QString("none"));
+ moduleProps = appRelease.moduleProperties();
+ if (moduleProps.getModuleProperty("cpp", "present").toBool())
+ QCOMPARE(moduleProps.getModuleProperty("cpp", "optimization").toString(), QString("fast"));
+
+ taskDescriptions.clear();
+ job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0));
+ connect(job.data(), &qbs::AbstractJob::taskStarted, taskDescHandler);
+ waitForFinished(job.data());
+ QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
+ QVERIFY2(!taskDescriptions.contains("Resolving"), qPrintable(taskDescriptions));
+
+ WAIT_FOR_NEW_TIMESTAMP();
+ QFile projectFile(setupParams.projectFilePath());
+ QVERIFY2(projectFile.open(QIODevice::ReadWrite), qPrintable(projectFile.errorString()));
+ QByteArray content = projectFile.readAll();
+ content.replace("\"clang\", \"llvm\", ", QByteArray());
+ projectFile.resize(0);
+ projectFile.write(content);
+ projectFile.close();
+ job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0));
+ waitForFinished(job.data());
+ QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
+ project = job->project().projectData();
+ products = project.allProducts();
+ QCOMPARE(products.count(), 4);
+ int clangProfiles = 0;
+ for (const qbs::ProductData &p : qAsConst(products)) {
+ if (p.profile() == "clangProfile") {
+ ++clangProfiles;
+ moduleProps = p.moduleProperties();
+ if (moduleProps.getModuleProperty("cpp", "present").toBool()) {
+ QCOMPARE(moduleProps.getModuleProperty("cpp", "cxxCompilerName").toString(),
+ QString("g++"));
+ }
+ }
+ }
+ QCOMPARE(clangProfiles, 1);
+}
+
+void TestApi::localProfiles_data()
+{
+ QTest::addColumn<bool>("enableProfiles");
+ QTest::newRow("profiles enabled") << true;
+ QTest::newRow("profiles disabled") << false;
+}
+
void TestApi::missingSourceFile()
{
qbs::SetupProjectParameters setupParams
diff --git a/tests/auto/api/tst_api.h b/tests/auto/api/tst_api.h
index beb0ab4ac..b70c69afb 100644
--- a/tests/auto/api/tst_api.h
+++ b/tests/auto/api/tst_api.h
@@ -100,6 +100,8 @@ private slots:
void linkDynamicAndStaticLibs();
void linkStaticAndDynamicLibs();
void listBuildSystemFiles();
+ void localProfiles();
+ void localProfiles_data();
void missingSourceFile();
void mocCppIncluded();
void multiArch();
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index c46f0c260..f6cc4b26f 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -10,6 +10,7 @@ qbs_enable_unit_tests {
SUBDIRS += \
cmdlineparser \
blackbox/blackbox.pro \
+ blackbox/blackbox-android.pro \
blackbox/blackbox-apple.pro \
blackbox/blackbox-clangdb.pro \
blackbox/blackbox-java.pro \
diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs
index 1840e07dd..92ab5279f 100644
--- a/tests/auto/auto.qbs
+++ b/tests/auto/auto.qbs
@@ -5,6 +5,7 @@ Project {
references: [
"api/api.qbs",
"blackbox/blackbox.qbs",
+ "blackbox/blackbox-android.qbs",
"blackbox/blackbox-apple.qbs",
"blackbox/blackbox-clangdb.qbs",
"blackbox/blackbox-java.qbs",
diff --git a/tests/auto/blackbox/blackbox-android.pro b/tests/auto/blackbox/blackbox-android.pro
new file mode 100644
index 000000000..d27550301
--- /dev/null
+++ b/tests/auto/blackbox/blackbox-android.pro
@@ -0,0 +1,18 @@
+TARGET = tst_blackbox-android
+
+HEADERS = tst_blackboxandroid.h tst_blackboxbase.h
+SOURCES = tst_blackboxandroid.cpp tst_blackboxbase.cpp
+OBJECTS_DIR = android
+MOC_DIR = $${OBJECTS_DIR}-moc
+
+include(../auto.pri)
+
+DATA_DIRS = testdata-android ../find
+
+for(data_dir, DATA_DIRS) {
+ files = $$files($$PWD/$$data_dir/*, true)
+ win32:files ~= s|\\\\|/|g
+ for(file, files):!exists($$file/*):FILES += $$file
+}
+
+OTHER_FILES += $$FILES
diff --git a/tests/auto/blackbox/blackbox-android.qbs b/tests/auto/blackbox/blackbox-android.qbs
new file mode 100644
index 000000000..4579ec862
--- /dev/null
+++ b/tests/auto/blackbox/blackbox-android.qbs
@@ -0,0 +1,22 @@
+import qbs
+
+QbsAutotest {
+ testName: "blackbox-android"
+ Depends { name: "qbs_app" }
+ Depends { name: "qbs-setup-toolchains" }
+ Group {
+ name: "testdata"
+ prefix: "testdata-android/"
+ files: ["**/*"]
+ fileTags: []
+ }
+ files: [
+ "../shared.h",
+ "tst_blackboxbase.cpp",
+ "tst_blackboxbase.h",
+ "tst_blackboxandroid.cpp",
+ "tst_blackboxandroid.h",
+ ]
+ // TODO: Use Utilities.cStringQuote
+ cpp.defines: base.concat(['SRCDIR="' + path + '"'])
+}
diff --git a/tests/auto/blackbox/blackbox.qbs b/tests/auto/blackbox/blackbox.qbs
index 0150876f7..c0fcbd471 100644
--- a/tests/auto/blackbox/blackbox.qbs
+++ b/tests/auto/blackbox/blackbox.qbs
@@ -5,6 +5,12 @@ QbsAutotest {
Depends { name: "qbs_app" }
Depends { name: "qbs-setup-toolchains" }
Group {
+ name: "find"
+ prefix: "find/"
+ files: ["**/*"]
+ fileTags: []
+ }
+ Group {
name: "testdata"
prefix: "testdata/"
files: ["**/*"]
diff --git a/tests/auto/blackbox/find/find-android.qbs b/tests/auto/blackbox/find/find-android.qbs
index 23c8b2f91..e164d6863 100644
--- a/tests/auto/blackbox/find/find-android.qbs
+++ b/tests/auto/blackbox/find/find-android.qbs
@@ -20,6 +20,7 @@ Product {
var tools = {};
if (product.moduleProperty("Android.sdk", "present")) {
tools["sdk"] = product.moduleProperty("Android.sdk", "sdkDir");
+ tools["sdk-build-tools-dx"] = product.Android.sdk.dxFilePath;
}
if (product.moduleProperty("Android.ndk", "present")) {
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/multiple-apks-per-project.qbs b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/multiple-apks-per-project.qbs
index 3d03c309a..3d03c309a 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/multiple-apks-per-project.qbs
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/multiple-apks-per-project.qbs
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/product1.qbs b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs
index 0ddd7526c..0ddd7526c 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/product1.qbs
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/AndroidManifest.xml
index 1468b52f6..289969409 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/AndroidManifest.xml
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/AndroidManifest.xml
@@ -3,10 +3,8 @@
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="19"/>
<uses-feature android:glEsVersion="0x00020000"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <application android:allowBackup="true" android:hasCode="true" android:name="io.qt.dummy">
- <activity android:name="dummy" android:debuggable="true" android:configChanges="orientation|keyboardHidden">
- <!-- Tell NativeActivity the name of or .so -->
- <meta-data android:name="android.app.lib_name" android:value="dummy1"/>
+ <application android:allowBackup="true" android:hasCode="true">
+ <activity android:name=".Dummy" android:debuggable="true" android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
diff --git a/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy1/Dummy.java b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy1/Dummy.java
new file mode 100644
index 000000000..18afe8301
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy1/Dummy.java
@@ -0,0 +1,19 @@
+package io.qt.dummy1;
+
+import android.app.Activity;
+import android.os.Build;
+import java.util.Arrays;
+import java.util.List;
+
+public class Dummy extends Activity
+{
+ static {
+ List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
+ if (abis.contains("x86") || abis.contains("mips")) {
+ System.loadLibrary("p1lib1");
+ }
+ if (abis.contains("armeabi")) {
+ System.loadLibrary("p1lib2");
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/jni/lib1.cpp b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib1.cpp
index 474897da9..474897da9 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/jni/lib1.cpp
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib1.cpp
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/jni/lib2.cpp b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib2.cpp
index de580ab09..de580ab09 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/jni/lib2.cpp
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib2.cpp
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/res/values/strings.xml b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/res/values/strings.xml
index 949748954..949748954 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/res/values/strings.xml
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/res/values/strings.xml
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/product2.qbs b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/product2.qbs
index f0418a425..f0418a425 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/product2.qbs
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/product2.qbs
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/AndroidManifest.xml
index 4e5cdf977..ef0fbe54f 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/AndroidManifest.xml
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/AndroidManifest.xml
@@ -3,10 +3,8 @@
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="19"/>
<uses-feature android:glEsVersion="0x00020000"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <application android:allowBackup="true" android:hasCode="true" android:name="io.qt.dummy">
- <activity android:name="dummy" android:debuggable="true" android:configChanges="orientation|keyboardHidden">
- <!-- Tell NativeActivity the name of or .so -->
- <meta-data android:name="android.app.lib_name" android:value="dummy2"/>
+ <application android:allowBackup="true" android:hasCode="true">
+ <activity android:name=".Dummy" android:debuggable="true" android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
diff --git a/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy2/Dummy.java b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy2/Dummy.java
new file mode 100644
index 000000000..b3659ae7a
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy2/Dummy.java
@@ -0,0 +1,17 @@
+package io.qt.dummy2;
+
+import android.app.Activity;
+import android.os.Build;
+import java.util.Arrays;
+import java.util.List;
+
+public class Dummy extends Activity
+{
+ static {
+ List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
+ if (abis.contains("armeabi")) {
+ System.loadLibrary("p2lib1");
+ System.loadLibrary("p2lib2");
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/jni/lib1.cpp b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib1.cpp
index 474897da9..474897da9 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/jni/lib1.cpp
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib1.cpp
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/jni/lib2.cpp b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib2.cpp
index de580ab09..de580ab09 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/jni/lib2.cpp
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib2.cpp
diff --git a/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/io/qbs/lib3/lib3.java b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/io/qbs/lib3/lib3.java
new file mode 100644
index 000000000..09ce152c3
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/io/qbs/lib3/lib3.java
@@ -0,0 +1,6 @@
+package io.qbs.lib3;
+
+public class lib3 {
+ public static void foo() {
+ }
+}
diff --git a/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib4.java b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib4.java
new file mode 100644
index 000000000..7b1de6c8b
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib4.java
@@ -0,0 +1,2 @@
+public class lib4 {
+}
diff --git a/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib5.java b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib5.java
new file mode 100644
index 000000000..92ab6dee2
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib5.java
@@ -0,0 +1,2 @@
+public class lib5 {
+}
diff --git a/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib6.java b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib6.java
new file mode 100644
index 000000000..c524967ee
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib6.java
@@ -0,0 +1,2 @@
+public class lib6 {
+}
diff --git a/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib7.java b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib7.java
new file mode 100644
index 000000000..110bab22c
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib7.java
@@ -0,0 +1,2 @@
+public class lib7 {
+}
diff --git a/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib8.java b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib8.java
new file mode 100644
index 000000000..e0cb448f0
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib8.java
@@ -0,0 +1,2 @@
+public class lib8 {
+}
diff --git a/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/multiple-libs-per-apk.qbs b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/multiple-libs-per-apk.qbs
new file mode 100644
index 000000000..d76a41232
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/multiple-libs-per-apk.qbs
@@ -0,0 +1,69 @@
+import qbs
+
+Project {
+ DynamicLibrary {
+ Depends { name: "Android.ndk" }
+ Depends { name: "cpp" }
+ name: "lib1"
+ files: ["src/main/jni/lib1.cpp"]
+ Android.ndk.appStl: "stlport_shared"
+ cpp.useRPaths: false
+ }
+
+ DynamicLibrary {
+ Depends { name: "Android.ndk" }
+ Depends { name: "cpp" }
+ name: "lib2"
+ files: ["src/main/jni/lib2.cpp"]
+ Android.ndk.appStl: "stlport_shared"
+ cpp.useRPaths: false
+ }
+
+ JavaJarFile {
+ Depends { name: "Android.sdk" }
+ Depends { name: "lib6" }
+ Depends { name: "lib8" }
+ name: "lib3"
+ files: ["io/qbs/lib3/lib3.java"]
+ }
+
+ JavaJarFile {
+ Depends { name: "Android.sdk" }
+ name: "lib4"
+ files: ["lib4.java"]
+ }
+
+ JavaJarFile {
+ Depends { name: "Android.sdk" }
+ name: "lib5"
+ files: ["lib5.java"]
+ }
+
+ JavaJarFile {
+ Depends { name: "Android.sdk" }
+ name: "lib6"
+ files: ["lib6.java"]
+ }
+
+ JavaJarFile {
+ Depends { name: "Android.sdk" }
+ name: "lib7"
+ files: ["lib7.java"]
+ }
+
+ JavaJarFile {
+ Depends { name: "Android.sdk" }
+ Depends { name: "lib7"; Android.sdk.embedJar: false }
+ name: "lib8"
+ files: ["lib8.java"]
+ }
+
+ AndroidApk {
+ name: "twolibs"
+ packageName: "io.qt.dummy"
+ Depends { productTypes: ["android.nativelibrary"] }
+ Depends { name: "lib3"; Android.sdk.embedJar: true }
+ Depends { name: "lib4"; Android.sdk.embedJar: false }
+ Depends { name: "lib5" }
+ }
+}
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/AndroidManifest.xml
index 639d3a647..6694afc18 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/AndroidManifest.xml
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/AndroidManifest.xml
@@ -3,10 +3,8 @@
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="19"/>
<uses-feature android:glEsVersion="0x00020000"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <application android:allowBackup="true" android:hasCode="true" android:name="io.qt.dummy">
- <activity android:name="dummy" android:debuggable="true" android:configChanges="orientation|keyboardHidden">
- <!-- Tell NativeActivity the name of or .so -->
- <meta-data android:name="android.app.lib_name" android:value="dummy"/>
+ <application android:allowBackup="true" android:hasCode="true">
+ <activity android:name=".Dummy" android:debuggable="true" android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
diff --git a/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java
new file mode 100644
index 000000000..56fd0db5b
--- /dev/null
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java
@@ -0,0 +1,19 @@
+package io.qt.dummy;
+
+import android.app.Activity;
+import android.os.Build;
+import io.qbs.lib3.lib3;
+import java.util.Arrays;
+import java.util.List;
+
+public class Dummy extends Activity
+{
+ static {
+ List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
+ if (abis.contains("armeabi")) {
+ System.loadLibrary("lib1");
+ System.loadLibrary("lib2");
+ }
+ lib3.foo();
+ }
+}
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/jni/lib1.cpp b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib1.cpp
index 474897da9..474897da9 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/jni/lib1.cpp
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib1.cpp
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/jni/lib2.cpp b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib2.cpp
index de580ab09..de580ab09 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/jni/lib2.cpp
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib2.cpp
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/res/values/strings.xml b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/res/values/strings.xml
index 297dfa7fa..297dfa7fa 100644
--- a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/res/values/strings.xml
+++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/res/values/strings.xml
diff --git a/tests/auto/blackbox/testdata-java/android/no-native/no-native.qbs b/tests/auto/blackbox/testdata-android/no-native/no-native.qbs
index 2909adc0c..2909adc0c 100644
--- a/tests/auto/blackbox/testdata-java/android/no-native/no-native.qbs
+++ b/tests/auto/blackbox/testdata-android/no-native/no-native.qbs
diff --git a/tests/auto/blackbox/testdata-java/android/teapot/teapot.qbs b/tests/auto/blackbox/testdata-android/teapot/teapot.qbs
index ff3077335..4bd8d8b7f 100644
--- a/tests/auto/blackbox/testdata-java/android/teapot/teapot.qbs
+++ b/tests/auto/blackbox/testdata-android/teapot/teapot.qbs
@@ -25,12 +25,30 @@ Project {
Depends { name: "cpp" }
Depends { name: "native-glue" }
+ Probe {
+ id: ndkHelperProbe
+ property string ndkDir: Android.ndk.ndkDir
+ property string samplesDir: Android.ndk.ndkSamplesDir
+ property string dir
+ configure: {
+ var paths = [samplesDir + "/teapots/common/ndk_helper/",
+ ndkDir + "/sources/android/ndk_helper/"];
+ for (var i = 0; i < paths.length; ++i) {
+ if (File.exists(paths[i])) {
+ dir = paths[i];
+ break;
+ }
+ }
+ }
+ }
+
Group {
id: ndkhelper_sources
- prefix: Android.ndk.ndkDir + "/sources/android/ndk_helper/"
+ prefix: ndkHelperProbe.dir
files: ["*.c", "*.cpp", "*.h"]
}
- Android.ndk.appStl: "stlport_shared"
+ Android.ndk.appStl: "gnustl_shared"
+ cpp.cxxLanguageVersion: "c++11"
Export {
Depends { name: "cpp" }
@@ -40,7 +58,7 @@ Project {
}
StaticLibrary {
- name: "cpufeatures"
+ name: "android_cpufeatures"
Depends { name: "cpp" }
Group {
id: cpufeatures_sources
@@ -59,7 +77,7 @@ Project {
name: "TeapotNativeActivity"
Depends { name: "Android.ndk" }
Depends { name: "cpp" }
- Depends { name: "cpufeatures" }
+ Depends { name: "android_cpufeatures" }
Depends { name: "native-glue" }
Depends { name: "ndk-helper" }
@@ -68,7 +86,8 @@ Project {
property string samplesDir: Android.ndk.ndkSamplesDir
property string jniDir
configure: {
- var paths = ["/teapots/classic-teapot/src/main/cpp/", "/Teapot/app/src/main/jni/"];
+ var paths = ["/teapots/classic-teapot/src/main/cpp/", "/Teapot/app/src/main/jni/",
+ "/Teapot/jni/"];
for (var i = 0; i < paths.length; ++i) {
if (File.exists(samplesDir + paths[i])) {
jniDir = samplesDir + paths[i];
@@ -91,9 +110,14 @@ Project {
FileTagger { patterns: ["*.inl"]; fileTags: ["hpp"] }
- Android.ndk.appStl: "stlport_shared"
+ Android.ndk.appStl: "gnustl_shared"
+ cpp.cxxLanguageVersion: "c++11"
cpp.dynamicLibraries: ["log", "android", "EGL", "GLESv2"]
cpp.useRPaths: false
+
+ // Export ANativeActivity_onCreate(),
+ // Refer to: https://github.com/android-ndk/ndk/issues/381
+ cpp.linkerFlags: ["-u", "ANativeActivity_onCreate"]
}
AndroidApk {
@@ -102,7 +126,7 @@ Project {
property string samplesDir: Android.sdk.ndkSamplesDir
property string dir
configure: {
- var paths = ["/teapots/classic-teapot/src/main", "/Teapot/app/src/main"];
+ var paths = ["/teapots/classic-teapot/src/main", "/Teapot/app/src/main", "/Teapot"];
for (var i = 0; i < paths.length; ++i) {
if (File.exists(samplesDir + paths[i])) {
dir = samplesDir + paths[i];
diff --git a/tests/auto/blackbox/testdata-apple/apple-multiconfig/apple-multiconfig.qbs b/tests/auto/blackbox/testdata-apple/apple-multiconfig/apple-multiconfig.qbs
index f34307bf1..c2b57645c 100644
--- a/tests/auto/blackbox/testdata-apple/apple-multiconfig/apple-multiconfig.qbs
+++ b/tests/auto/blackbox/testdata-apple/apple-multiconfig/apple-multiconfig.qbs
@@ -98,6 +98,24 @@ Project {
}
}
+ CppApplication {
+ Depends { name: "multilib" }
+ Depends { name: "bundle" }
+ name: "fatmultiappmultivariant"
+ targetName: "fatmultiappmultivariant"
+ files: ["app.c"]
+ cpp.rpaths: qbs.targetOS.contains("darwin") ? ["@loader_path/../../../"] : []
+ cpp.minimumMacosVersion: "10.5"
+ qbs.architectures: ["x86", "x86_64"]
+ qbs.buildVariants: ["debug", "profile"]
+
+ Group {
+ fileTagsFilter: ["bundle.content"]
+ qbs.install: true
+ qbs.installSourceBase: product.buildDirectory
+ }
+ }
+
DynamicLibrary {
Depends { name: "cpp" }
Depends { name: "bundle" }
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy/Dummy.java b/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy/Dummy.java
deleted file mode 100644
index 149af081f..000000000
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy/Dummy.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.qt.dummy;
-
-import android.app.Activity;
-
-public class Dummy extends Activity
-{
- static {
- System.loadLibrary("lib1");
- System.loadLibrary("lib");
- }
-}
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy/Dummy.java b/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy/Dummy.java
deleted file mode 100644
index 149af081f..000000000
--- a/tests/auto/blackbox/testdata-java/android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy/Dummy.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.qt.dummy;
-
-import android.app.Activity;
-
-public class Dummy extends Activity
-{
- static {
- System.loadLibrary("lib1");
- System.loadLibrary("lib");
- }
-}
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/multiple-libs-per-apk.qbs b/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/multiple-libs-per-apk.qbs
deleted file mode 100644
index 94eb33ce7..000000000
--- a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/multiple-libs-per-apk.qbs
+++ /dev/null
@@ -1,27 +0,0 @@
-import qbs
-
-Project {
- DynamicLibrary {
- Depends { name: "Android.ndk" }
- Depends { name: "cpp" }
- name: "lib1"
- files: ["src/main/jni/lib1.cpp"]
- Android.ndk.appStl: "stlport_shared"
- cpp.useRPaths: false
- }
-
- DynamicLibrary {
- Depends { name: "Android.ndk" }
- Depends { name: "cpp" }
- name: "lib2"
- files: ["src/main/jni/lib2.cpp"]
- Android.ndk.appStl: "stlport_shared"
- cpp.useRPaths: false
- }
-
- AndroidApk {
- name: "twolibs"
- packageName: "io.qt.dummy"
- Depends { productTypes: ["android.nativelibrary"] }
- }
-}
diff --git a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java b/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java
deleted file mode 100644
index 149af081f..000000000
--- a/tests/auto/blackbox/testdata-java/android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.qt.dummy;
-
-import android.app.Activity;
-
-public class Dummy extends Activity
-{
- static {
- System.loadLibrary("lib1");
- System.loadLibrary("lib");
- }
-}
diff --git a/tests/auto/blackbox/testdata-qt/cached-qml/MainForm.ui.qml b/tests/auto/blackbox/testdata-qt/cached-qml/MainForm.ui.qml
new file mode 100644
index 000000000..3fe8b6f04
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/cached-qml/MainForm.ui.qml
@@ -0,0 +1,29 @@
+import QtQuick 2.6
+
+Rectangle {
+ property alias mouseArea: mouseArea
+ property alias textEdit: textEdit
+
+ width: 360
+ height: 360
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ }
+
+ TextEdit {
+ id: textEdit
+ text: qsTr("Enter some text...")
+ verticalAlignment: Text.AlignVCenter
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.topMargin: 20
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: -10
+ color: "transparent"
+ border.width: 1
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs b/tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs
new file mode 100644
index 000000000..398d3f4a9
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs
@@ -0,0 +1,30 @@
+import qbs
+
+CppApplication {
+ name: "app"
+ Depends { name: "Qt.core" }
+ Depends { name: "Qt.quick" }
+ Depends { name: "Qt.qml" }
+ Qt.qml.generateCacheFiles: true
+ Qt.qml.cacheFilesInstallDir: "data"
+
+ files: [
+ "main.cpp",
+ "MainForm.ui.qml",
+ "main.qml",
+ "stuff.js"
+ ]
+
+ Group {
+ fileTagsFilter: ["application"]
+ qbs.install: true
+ }
+
+ // Install the C++ sources to tell the blackbox test that Qt.qmlcache is not available.
+ Group {
+ condition: !Qt.qml.cachingEnabled
+ fileTagsFilter: ["cpp"]
+ qbs.install: true
+ qbs.installDir: "data"
+ }
+}
diff --git a/tests/auto/blackbox/testdata-qt/cached-qml/main.cpp b/tests/auto/blackbox/testdata-qt/cached-qml/main.cpp
new file mode 100644
index 000000000..ac82ac0cf
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/cached-qml/main.cpp
@@ -0,0 +1,14 @@
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(qApp->applicationDirPath() + QStringLiteral("/data/main.qml")));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ return app.exec();
+}
diff --git a/tests/auto/blackbox/testdata-qt/cached-qml/main.qml b/tests/auto/blackbox/testdata-qt/cached-qml/main.qml
new file mode 100644
index 000000000..f5f32f6bd
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/cached-qml/main.qml
@@ -0,0 +1,17 @@
+import QtQuick 2.6
+import QtQuick.Window 2.2
+import "stuff.js" as Stuff
+
+Window {
+ visible: true
+ width: 640
+ height: 480
+ title: Stuff.title()
+
+ MainForm {
+ anchors.fill: parent
+ mouseArea.onClicked: {
+ console.log(qsTr('Clicked on background. Text: "' + textEdit.text + '"'))
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata-qt/cached-qml/qml.qrc b/tests/auto/blackbox/testdata-qt/cached-qml/qml.qrc
new file mode 100644
index 000000000..57c8988b9
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/cached-qml/qml.qrc
@@ -0,0 +1,7 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+ <qresource prefix="/">
+ <file>main.qml</file>
+ <file>MainForm.ui.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/auto/blackbox/testdata-qt/cached-qml/stuff.js b/tests/auto/blackbox/testdata-qt/cached-qml/stuff.js
new file mode 100644
index 000000000..3c19feacd
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/cached-qml/stuff.js
@@ -0,0 +1 @@
+function title() { return "Wello Horld!"; }
diff --git a/tests/manual/pkgconfig/main.cpp b/tests/auto/blackbox/testdata-qt/pkgconfig/main.cpp
index c50a897ae..2080b5077 100644
--- a/tests/manual/pkgconfig/main.cpp
+++ b/tests/auto/blackbox/testdata-qt/pkgconfig/main.cpp
@@ -26,11 +26,21 @@
**
****************************************************************************/
+#ifdef QT_CORE_LIB
#include <QCoreApplication>
#include <QDebug>
+#else
+#include <iostream>
+#endif
int main(int argc, char **argv)
{
+#ifdef QT_CORE_LIB
QCoreApplication app(argc, argv);
qDebug() << "Hello world!";
+#else
+ (void)argc;
+ (void)argv;
+ std::cout << "Skip this test" << std::endl;
+#endif
}
diff --git a/tests/manual/pkgconfig/pkgconfig.qbs b/tests/auto/blackbox/testdata-qt/pkgconfig/pkgconfig.qbs
index 132ce337a..132ce337a 100644
--- a/tests/manual/pkgconfig/pkgconfig.qbs
+++ b/tests/auto/blackbox/testdata-qt/pkgconfig/pkgconfig.qbs
diff --git a/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.cpp b/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.cpp
new file mode 100644
index 000000000..aba40ccd6
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.cpp
@@ -0,0 +1,10 @@
+#include "file.h"
+
+MyObject::MyObject() {}
+
+int main()
+{
+ MyObject o1;
+}
+
+#include <moc_file.cpp>
diff --git a/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.h b/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.h
new file mode 100644
index 000000000..4333b64a6
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <QObject>
+
+class MyObject : public QObject
+{
+ Q_OBJECT
+ public:
+ MyObject();
+
+ signals:
+ void someSignal();
+};
diff --git a/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/remove-moc-header-from-file-list.qbs b/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/remove-moc-header-from-file-list.qbs
new file mode 100644
index 000000000..120f10b0d
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/remove-moc-header-from-file-list.qbs
@@ -0,0 +1,9 @@
+import qbs
+
+QtApplication {
+ name: "p"
+ files: [
+ "file.h",
+ "file.cpp"
+ ]
+}
diff --git a/tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/add-filetag-to-generated-artifact.qbs b/tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/add-filetag-to-generated-artifact.qbs
new file mode 100644
index 000000000..1bc9b15ef
--- /dev/null
+++ b/tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/add-filetag-to-generated-artifact.qbs
@@ -0,0 +1,34 @@
+import qbs
+import qbs.File
+
+Project {
+ CppApplication {
+ name: "my_app"
+ files: "main.cpp"
+ Group {
+ fileTagsFilter: ["application"]
+ fileTags: ["app-to-compress"]
+ }
+ }
+ Product {
+ name: "my_compressed_app"
+ type: ["compressed_application"]
+ Depends { name: "my_app" }
+ Rule {
+ inputsFromDependencies: ["app-to-compress"]
+ Artifact {
+ filePath: "compressed-" + input.fileName
+ fileTags: ["compressed_application"]
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "compressing " + input.fileName;
+ cmd.highlight = "linker";
+ cmd.sourceCode = function () {
+ File.copy(input.filePath, output.filePath);
+ };
+ return cmd;
+ }
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/main.cpp b/tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/main.cpp
new file mode 100644
index 000000000..237c8ce18
--- /dev/null
+++ b/tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/main.cpp
@@ -0,0 +1 @@
+int main() {}
diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/CppDefinesApp.qbs b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/CppDefinesApp.qbs
new file mode 100644
index 000000000..da58f750a
--- /dev/null
+++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/CppDefinesApp.qbs
@@ -0,0 +1,24 @@
+import qbs
+
+CppApplication {
+ files: ["app.c"]
+
+ property bool enableObjectiveC: qbs.targetOS.contains("darwin")
+
+ Group {
+ name: "C/C++"
+ files: [
+ "test.c",
+ "test.cpp",
+ ]
+ }
+
+ Group {
+ name: "Objective-C"
+ condition: enableObjectiveC
+ files: [
+ "test.m",
+ "test.mm",
+ ]
+ }
+}
diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/app.c b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/app.c
new file mode 100644
index 000000000..905869dfa
--- /dev/null
+++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/app.c
@@ -0,0 +1,4 @@
+int main()
+{
+ return 0;
+}
diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/compilerDefinesByLanguage.qbs b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/compilerDefinesByLanguage.qbs
new file mode 100644
index 000000000..b4b126a50
--- /dev/null
+++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/compilerDefinesByLanguage.qbs
@@ -0,0 +1,171 @@
+import qbs
+
+Project {
+ CppDefinesApp {
+ name: "A"
+ // eqiuvalent to ["c"] since we always need some compiler defines
+ // for the architecture detection, etc.
+ cpp.enableCompilerDefinesByLanguage: []
+ property var foo: {
+ if (!cpp.compilerDefinesByLanguage)
+ throw "ASSERT cpp.compilerDefinesByLanguage: "
+ + cpp.compilerDefinesByLanguage;
+ if (!cpp.compilerDefinesByLanguage["c"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"c\"]: "
+ + cpp.compilerDefinesByLanguage["c"];
+ if (cpp.compilerDefinesByLanguage["cpp"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"cpp\"]: "
+ + cpp.compilerDefinesByLanguage["cpp"];
+ if (cpp.compilerDefinesByLanguage["objc"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objc\"]: "
+ + cpp.compilerDefinesByLanguage["objc"];
+ if (cpp.compilerDefinesByLanguage["objcpp"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objcpp\"]: "
+ + cpp.compilerDefinesByLanguage["objcpp"];
+ }
+ }
+
+ CppDefinesApp {
+ name: "B"
+ cpp.enableCompilerDefinesByLanguage: ["cpp"]
+ property var foo: {
+ if (!cpp.compilerDefinesByLanguage)
+ throw "ASSERT cpp.compilerDefinesByLanguage: "
+ + cpp.compilerDefinesByLanguage;
+ if (cpp.compilerDefinesByLanguage["c"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"c\"]: "
+ + cpp.compilerDefinesByLanguage["c"];
+ if (!cpp.compilerDefinesByLanguage["cpp"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"cpp\"]: "
+ + cpp.compilerDefinesByLanguage["cpp"];
+ if (cpp.compilerDefinesByLanguage["objc"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objc\"]: "
+ + cpp.compilerDefinesByLanguage["objc"];
+ if (cpp.compilerDefinesByLanguage["objcpp"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objcpp\"]: "
+ + cpp.compilerDefinesByLanguage["objcpp"];
+ }
+ }
+
+ CppDefinesApp {
+ name: "C"
+ condition: enableObjectiveC
+ cpp.enableCompilerDefinesByLanguage: ["objc"]
+ property var foo: {
+ if (!enableObjectiveC)
+ return;
+ if (!cpp.compilerDefinesByLanguage)
+ throw "ASSERT cpp.compilerDefinesByLanguage: "
+ + cpp.compilerDefinesByLanguage;
+ if (cpp.compilerDefinesByLanguage["c"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"c\"]: "
+ + cpp.compilerDefinesByLanguage["c"];
+ if (cpp.compilerDefinesByLanguage["cpp"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"cpp\"]: "
+ + cpp.compilerDefinesByLanguage["cpp"];
+ if (!cpp.compilerDefinesByLanguage["objc"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"objc\"]: "
+ + cpp.compilerDefinesByLanguage["objc"];
+ if (cpp.compilerDefinesByLanguage["objcpp"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objcpp\"]: "
+ + cpp.compilerDefinesByLanguage["objcpp"];
+ }
+ }
+
+ CppDefinesApp {
+ name: "D"
+ condition: enableObjectiveC
+ cpp.enableCompilerDefinesByLanguage: ["objcpp"]
+ property var foo: {
+ if (!enableObjectiveC)
+ return;
+ if (!cpp.compilerDefinesByLanguage)
+ throw "ASSERT cpp.compilerDefinesByLanguage: "
+ + cpp.compilerDefinesByLanguage;
+ if (cpp.compilerDefinesByLanguage["c"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"c\"]: "
+ + cpp.compilerDefinesByLanguage["c"];
+ if (cpp.compilerDefinesByLanguage["cpp"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"cpp\"]: "
+ + cpp.compilerDefinesByLanguage["cpp"];
+ if (cpp.compilerDefinesByLanguage["objc"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objc\"]: "
+ + cpp.compilerDefinesByLanguage["objc"];
+ if (!cpp.compilerDefinesByLanguage["objcpp"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"objcpp\"]: "
+ + cpp.compilerDefinesByLanguage["objcpp"];
+ }
+ }
+
+ CppDefinesApp {
+ name: "E"
+ condition: enableObjectiveC
+ cpp.enableCompilerDefinesByLanguage: ["cpp", "objcpp"]
+ property var foo: {
+ if (!enableObjectiveC)
+ return;
+ if (!cpp.compilerDefinesByLanguage)
+ throw "ASSERT cpp.compilerDefinesByLanguage: "
+ + cpp.compilerDefinesByLanguage;
+ if (cpp.compilerDefinesByLanguage["c"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"c\"]: "
+ + cpp.compilerDefinesByLanguage["c"];
+ if (!cpp.compilerDefinesByLanguage["cpp"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"cpp\"]: "
+ + cpp.compilerDefinesByLanguage["cpp"];
+ if (cpp.compilerDefinesByLanguage["objc"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objc\"]: "
+ + cpp.compilerDefinesByLanguage["objc"];
+ if (!cpp.compilerDefinesByLanguage["objcpp"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"objcpp\"]: "
+ + cpp.compilerDefinesByLanguage["objcpp"];
+ }
+ }
+
+ CppDefinesApp {
+ name: "F"
+ cpp.enableCompilerDefinesByLanguage: ["c", "cpp"]
+ property var foo: {
+ if (!cpp.compilerDefinesByLanguage)
+ throw "ASSERT cpp.compilerDefinesByLanguage: "
+ + cpp.compilerDefinesByLanguage;
+ if (!cpp.compilerDefinesByLanguage["c"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"c\"]: "
+ + cpp.compilerDefinesByLanguage["c"];
+ if (!cpp.compilerDefinesByLanguage["cpp"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"cpp\"]: "
+ + cpp.compilerDefinesByLanguage["cpp"];
+ if (cpp.compilerDefinesByLanguage["objc"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objc\"]: "
+ + cpp.compilerDefinesByLanguage["objc"];
+ if (cpp.compilerDefinesByLanguage["objcpp"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"objcpp\"]: "
+ + cpp.compilerDefinesByLanguage["objcpp"];
+ }
+ }
+
+ CppDefinesApp {
+ name: "G"
+ condition: enableObjectiveC
+ cpp.enableCompilerDefinesByLanguage: ["objc", "objcpp"]
+ property var foo: {
+ if (!enableObjectiveC)
+ return;
+ if (!cpp.compilerDefinesByLanguage)
+ throw "ASSERT cpp.compilerDefinesByLanguage: "
+ + cpp.compilerDefinesByLanguage;
+ if (cpp.compilerDefinesByLanguage["c"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"c\"]: "
+ + cpp.compilerDefinesByLanguage["c"];
+ if (cpp.compilerDefinesByLanguage["cpp"])
+ throw "ASSERT !cpp.compilerDefinesByLanguage[\"cpp\"]: "
+ + cpp.compilerDefinesByLanguage["cpp"];
+ if (!cpp.compilerDefinesByLanguage["objc"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"objc\"]: "
+ + cpp.compilerDefinesByLanguage["objc"];
+ if (!cpp.compilerDefinesByLanguage["objcpp"])
+ throw "ASSERT cpp.compilerDefinesByLanguage[\"objcpp\"]: "
+ + cpp.compilerDefinesByLanguage["objcpp"];
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.c b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.c
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.c
diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.cpp b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.cpp
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.cpp
diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.m b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.m
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.m
diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.mm b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.mm
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.mm
diff --git a/tests/manual/configure/configure.qbs b/tests/auto/blackbox/testdata/configure/configure.qbs
index 1a3c02bb5..1a3c02bb5 100644
--- a/tests/manual/configure/configure.qbs
+++ b/tests/auto/blackbox/testdata/configure/configure.qbs
diff --git a/tests/manual/configure/main.cpp b/tests/auto/blackbox/testdata/configure/main.cpp
index c7213c768..c7213c768 100644
--- a/tests/manual/configure/main.cpp
+++ b/tests/auto/blackbox/testdata/configure/main.cpp
diff --git a/tests/manual/configure/modules/definition/module.qbs b/tests/auto/blackbox/testdata/configure/modules/definition/module.qbs
index 60f442149..60f442149 100644
--- a/tests/manual/configure/modules/definition/module.qbs
+++ b/tests/auto/blackbox/testdata/configure/modules/definition/module.qbs
diff --git a/tests/auto/blackbox/testdata/cpu-features/cpu-features.qbs b/tests/auto/blackbox/testdata/cpu-features/cpu-features.qbs
new file mode 100644
index 000000000..62f163a95
--- /dev/null
+++ b/tests/auto/blackbox/testdata/cpu-features/cpu-features.qbs
@@ -0,0 +1,16 @@
+import qbs
+
+CppApplication {
+ Depends { name: "cpufeatures" }
+ cpufeatures.x86_sse2: true
+ cpufeatures.x86_avx: true
+ cpufeatures.x86_avx512f: false
+
+ files: ["main.cpp"]
+ property bool dummy: {
+ console.info("is x86: " + (qbs.architecture === "x86"));
+ console.info("is x64: " + (qbs.architecture === "x86_64"));
+ console.info("is gcc: " + qbs.toolchain.contains("gcc"));
+ console.info("is msvc: " + qbs.toolchain.contains("msvc"));
+ }
+}
diff --git a/tests/auto/blackbox/testdata/cpu-features/main.cpp b/tests/auto/blackbox/testdata/cpu-features/main.cpp
new file mode 100644
index 000000000..237c8ce18
--- /dev/null
+++ b/tests/auto/blackbox/testdata/cpu-features/main.cpp
@@ -0,0 +1 @@
+int main() {}
diff --git a/tests/auto/blackbox/testdata/discard-unused-data/discard-unused-data.qbs b/tests/auto/blackbox/testdata/discard-unused-data/discard-unused-data.qbs
new file mode 100644
index 000000000..6f433cf63
--- /dev/null
+++ b/tests/auto/blackbox/testdata/discard-unused-data/discard-unused-data.qbs
@@ -0,0 +1,32 @@
+import qbs
+
+CppApplication {
+ name: "app"
+ type: base.concat("custom")
+
+ files: "main.cpp"
+
+ Depends { name: "bundle"; condition: qbs.targetOS.contains("darwin") }
+ Properties {
+ condition: qbs.targetOS.contains("darwin")
+ bundle.isBundle: false
+ }
+
+ Rule {
+ multiplex: true
+ Artifact {
+ filePath: "dummy.txt"
+ fileTags: ["custom"]
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.silent = true;
+ cmd.sourceCode = function() {
+ console.info("is Darwin: " + product.qbs.targetOS.contains("darwin"));
+ console.info("---" + product.cpp.nmPath + "---");
+ }
+ return [cmd];
+ }
+ }
+
+}
diff --git a/tests/auto/blackbox/testdata/discard-unused-data/main.cpp b/tests/auto/blackbox/testdata/discard-unused-data/main.cpp
new file mode 100644
index 000000000..e3aab2664
--- /dev/null
+++ b/tests/auto/blackbox/testdata/discard-unused-data/main.cpp
@@ -0,0 +1,3 @@
+void unusedFunc() {}
+
+int main() {}
diff --git a/tests/auto/blackbox/testdata/external-libs/external-libs.qbs b/tests/auto/blackbox/testdata/external-libs/external-libs.qbs
index 5b9e12ebb..d2e983c3e 100644
--- a/tests/auto/blackbox/testdata/external-libs/external-libs.qbs
+++ b/tests/auto/blackbox/testdata/external-libs/external-libs.qbs
@@ -24,47 +24,11 @@ Project {
}
files: ["lib2.cpp"]
}
- // TODO: Remove once we have parameterized dependencies
- Product {
- name: "barrier"
- type: ["blubb"]
- Depends { name: "lib1" }
- Depends { name: "lib2" }
- Rule {
- multiplex: true
- inputsFromDependencies: ["staticlibrary"]
- Artifact {
- filePath: "dummy"
- fileTags: ["blubb"]
- }
- prepare: {
- var cmd = new JavaScriptCommand();
- cmd.silent = true;
- cmd.sourceCode = function() { }
- return [cmd];
- }
- }
- }
CppApplication {
- Depends { name: "barrier" }
+ Depends { name: "lib1"; cpp.link: false }
+ Depends { name: "lib2"; cpp.link: false }
files: ["main.cpp"]
cpp.libraryPaths: [project.libDir]
cpp.staticLibraries: ["lib1", "lib2", "lib1"]
- Rule {
- inputsFromDependencies: ["blubb"]
- Artifact {
- filePath: "dummy.cpp"
- fileTags: ["cpp"]
- }
- prepare: {
- var cmd = new JavaScriptCommand();
- cmd.sourceCode = function() {
- var f = new TextFile(output.filePath, TextFile.WriteOnly);
- f.writeLine("void dummy() { }");
- f.close();
- };
- return [cmd];
- }
- }
}
}
diff --git a/tests/manual/includeLookup/includeLookup.qbs b/tests/auto/blackbox/testdata/includeLookup/includeLookup.qbs
index 452e4b5bd..452e4b5bd 100644
--- a/tests/manual/includeLookup/includeLookup.qbs
+++ b/tests/auto/blackbox/testdata/includeLookup/includeLookup.qbs
diff --git a/tests/manual/includeLookup/main.cpp b/tests/auto/blackbox/testdata/includeLookup/main.cpp
index c7213c768..c7213c768 100644
--- a/tests/manual/includeLookup/main.cpp
+++ b/tests/auto/blackbox/testdata/includeLookup/main.cpp
diff --git a/tests/auto/blackbox/testdata/includeLookup/modules/definition/fakeopenssl/sha.h b/tests/auto/blackbox/testdata/includeLookup/modules/definition/fakeopenssl/sha.h
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/includeLookup/modules/definition/fakeopenssl/sha.h
diff --git a/tests/manual/includeLookup/modules/definition/module.qbs b/tests/auto/blackbox/testdata/includeLookup/modules/definition/module.qbs
index 87b0d0eab..e894cfd49 100644
--- a/tests/manual/includeLookup/modules/definition/module.qbs
+++ b/tests/auto/blackbox/testdata/includeLookup/modules/definition/module.qbs
@@ -4,9 +4,11 @@ import qbs.Probes
Module {
name: 'definition'
Depends { name: 'cpp' }
+ property string modulePath: path
Probes.IncludeProbe {
id: includeNode
- names: "openssl/sha.h"
+ names: "fakeopenssl/sha.h"
+ platformPaths: [modulePath]
}
cpp.defines: includeNode.found ? 'TEXT="' + includeNode.path + '"' : undefined
}
diff --git a/tests/auto/blackbox/testdata/installable/installable.qbs b/tests/auto/blackbox/testdata/installable/installable.qbs
index c9d579f60..7ce41867b 100644
--- a/tests/auto/blackbox/testdata/installable/installable.qbs
+++ b/tests/auto/blackbox/testdata/installable/installable.qbs
@@ -11,7 +11,7 @@ Project {
}
Group {
- fileTagsFilter: product.type
+ fileTagsFilter: ["application"]
qbs.install: true
}
}
diff --git a/tests/auto/blackbox/testdata/installpackage/installpackage.qbs b/tests/auto/blackbox/testdata/installpackage/installpackage.qbs
index 0468e6bc0..d04ecdd57 100644
--- a/tests/auto/blackbox/testdata/installpackage/installpackage.qbs
+++ b/tests/auto/blackbox/testdata/installpackage/installpackage.qbs
@@ -3,6 +3,7 @@ import qbs
Project {
CppApplication {
name: "public_tool"
+ bundle.isBundle: false
Depends { name: "mylib" }
files: ["main.cpp"]
Group {
@@ -17,6 +18,7 @@ Project {
files: ["main.cpp"]
}
DynamicLibrary {
+ bundle.isBundle: false
Depends { name: "cpp" }
name: "mylib"
files: ["lib.cpp"]
diff --git a/tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs b/tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs
index 388eef4e0..cf28b4014 100644
--- a/tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs
+++ b/tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs
@@ -17,6 +17,7 @@ Product {
var output = new TextFile(FileInfo.joinPaths(product.sourceDirectory, "output.txt"),
TextFile.WriteOnly);
output.writeLine(FileInfo.baseName("/tmp/blubb.tar.gz"));
+ output.writeLine(FileInfo.cleanPath("/usr/local//../bin/"));
output.writeLine(FileInfo.completeBaseName("/tmp/blubb.tar.gz"));
output.writeLine(FileInfo.fileName("/tmp/blubb.tar.gz"));
output.writeLine(FileInfo.fromWindowsSeparators("/tmp/blubb.tar.gz"));
diff --git a/tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs b/tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs
index 456a10f0d..ef433a1e4 100644
--- a/tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs
+++ b/tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs
@@ -10,11 +10,20 @@ Product {
fileTags: ["dummy"]
}
prepare: {
+ var commands = [];
var cmd = new JavaScriptCommand();
cmd.silent = true;
cmd.sourceCode = function() {
var file1 = new TextFile("file1.txt", TextFile.WriteOnly);
- file1.write("First line.\nSecond line.\nThird line.");
+ file1.write("First line.\n");
+ // Do not close the file to test the auto close functionality.
+ };
+ commands.push(cmd);
+ cmd = new JavaScriptCommand();
+ cmd.silent = true;
+ cmd.sourceCode = function() {
+ var file1 = new TextFile("file1.txt", TextFile.WriteOnly | TextFile.Append);
+ file1.write("Second line.\nThird line.");
file1.close();
file1 = new TextFile("file1.txt", TextFile.ReadWrite);
var file2 = new TextFile("file2.txt", TextFile.WriteOnly);
@@ -26,11 +35,13 @@ Product {
file2.writeLine(line);
}
file1.truncate();
+ file2.writeLine(file1.filePath());
file2.writeLine(file1.atEof());
file1.close();
file2.close();
};
- return [cmd];
+ commands.push(cmd);
+ return commands;
}
}
}
diff --git a/tests/auto/blackbox/testdata/list-products/list-products.qbs b/tests/auto/blackbox/testdata/list-products/list-products.qbs
new file mode 100644
index 000000000..559a7d274
--- /dev/null
+++ b/tests/auto/blackbox/testdata/list-products/list-products.qbs
@@ -0,0 +1,16 @@
+import qbs
+
+Project {
+ Product {
+ name: "a"
+ }
+ Product {
+ name: "b"
+ multiplexByQbsProperties: ["architectures", "buildVariants"]
+ qbs.architectures: ["mips", "vax"]
+ qbs.buildVariants: ["debug", "release"]
+ }
+ Product {
+ name: "c"
+ }
+}
diff --git a/tests/manual/localDeployment/localDeployment.qbs b/tests/auto/blackbox/testdata/localDeployment/localDeployment.qbs
index 83f02120b..2b4d1f241 100644
--- a/tests/manual/localDeployment/localDeployment.qbs
+++ b/tests/auto/blackbox/testdata/localDeployment/localDeployment.qbs
@@ -7,7 +7,14 @@ Project {
name: "HelloWorld"
destinationDirectory: "bin"
- Depends { name: "Qt.core"}
+ Depends { name: "cpp" }
+ cpp.cxxLanguageVersion: "c++11"
+
+ Group {
+ fileTagsFilter: product.type
+ qbs.install: true
+ qbs.installDir: "bin"
+ }
Group {
qbs.install: true
diff --git a/tests/auto/blackbox/testdata/localDeployment/main.cpp b/tests/auto/blackbox/testdata/localDeployment/main.cpp
new file mode 100644
index 000000000..c991b6d27
--- /dev/null
+++ b/tests/auto/blackbox/testdata/localDeployment/main.cpp
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <fstream>
+#include <iostream>
+#include <string>
+
+int main(int argc, char *argv[])
+{
+ if (argc != 1)
+ return 1;
+
+ std::string s = argv[0];
+ for (auto &c : s) {
+ if (c == '\\')
+ c = '/';
+ }
+ const std::string mainFilePath =
+ std::string(s.substr(0, s.find_last_of("/")) + "/../share/main.cpp");
+ std::ifstream in(mainFilePath.c_str());
+ if (!in.is_open()) {
+ std::cerr << "Failed to open file: " << mainFilePath;
+ return 1;
+ }
+ std::string str((std::istreambuf_iterator<char>(in)),
+ std::istreambuf_iterator<char>());
+ std::cout << str << std::endl;
+ return 0;
+}
diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/fakewindows.qbs b/tests/auto/blackbox/testdata/minimumSystemVersion/fakewindows.qbs
new file mode 100644
index 000000000..f536da6ac
--- /dev/null
+++ b/tests/auto/blackbox/testdata/minimumSystemVersion/fakewindows.qbs
@@ -0,0 +1,16 @@
+import qbs
+import qbs.Utilities
+
+// non-existent versions of Windows should print a QBS warning
+// (but will still compile and link since we avoid passing a
+// bad value to the linker)
+CppApplication {
+ condition: qbs.targetOS.contains("windows")
+ files: ["main.cpp"]
+ consoleApplication: true
+ cpp.minimumWindowsVersion: "5.3"
+ cpp.defines: [
+ "QBS_WINVER=0x503",
+ "TOOLCHAIN_INSTALL_PATH=" + Utilities.cStringQuote(cpp.toolchainInstallPath)
+ ]
+}
diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/macappstore.qbs b/tests/auto/blackbox/testdata/minimumSystemVersion/macappstore.qbs
new file mode 100644
index 000000000..3f214fed2
--- /dev/null
+++ b/tests/auto/blackbox/testdata/minimumSystemVersion/macappstore.qbs
@@ -0,0 +1,12 @@
+import qbs
+
+// just to make sure three-digit minimum versions work on macOS
+// this only affects the value of __MAC_OS_X_VERSION_MIN_REQUIRED,
+// not the actual LC_VERSION_MIN_MACOSX command which is limited to two
+CppApplication {
+ condition: qbs.targetOS.contains("macos")
+ files: ["main.mm"]
+ consoleApplication: true
+ cpp.frameworks: "Foundation"
+ cpp.minimumMacosVersion: "10.6.8"
+}
diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/main.cpp b/tests/auto/blackbox/testdata/minimumSystemVersion/main.cpp
new file mode 100644
index 000000000..4cc99756b
--- /dev/null
+++ b/tests/auto/blackbox/testdata/minimumSystemVersion/main.cpp
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifdef _WIN32
+#include <algorithm>
+#include <cctype>
+#include <cstring>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <string>
+#endif
+
+int main(int argc, char *argv[])
+{
+ if (argc != 1)
+ return 1;
+
+#ifdef _WIN32
+#if defined(WINVER) && defined(QBS_WINVER)
+ std::cout << "WINVER=" << WINVER << std::endl;
+ std::string command = TOOLCHAIN_INSTALL_PATH;
+ std::replace(command.begin(), command.end(), '/', '\\');
+ command = "\"\"" + command;
+#ifdef __GNUC__
+ command += "\\objdump.exe\" -p \"";
+#else
+ command += "\\dumpbin.exe\" /HEADERS \"";
+#endif
+ command += argv[0];
+ command += "\" > qbs-test-dumpbin.txt\"";
+ int status = ::system(command.c_str());
+ if (status != 0)
+ return status;
+
+ std::ifstream in("qbs-test-dumpbin.txt");
+ std::string s;
+ while (std::getline(in, s)) {
+#ifdef __GNUC__
+ static const char *majorOSystemVersion = "MajorOSystemVersion\t";
+ if (s.find(majorOSystemVersion) != std::string::npos)
+ std::cout << s.substr(std::strlen(majorOSystemVersion));
+
+ static const char *minorOSystemVersion = "MinorOSystemVersion\t";
+ if (s.find(minorOSystemVersion) != std::string::npos)
+ std::cout << ".0" << s.substr(std::strlen(minorOSystemVersion))
+ << " operating system version" << std::endl;
+
+ static const char *majorSubsystemVersion = "MajorSubsystemVersion\t";
+ if (s.find(majorSubsystemVersion) != std::string::npos)
+ std::cout << s.substr(std::strlen(majorSubsystemVersion));
+
+ static const char *minorSubsystemVersion = "MinorSubsystemVersion\t";
+ if (s.find(minorSubsystemVersion) != std::string::npos)
+ std::cout << ".0" << s.substr(std::strlen(minorSubsystemVersion))
+ << " subsystem version" << std::endl;
+#else
+ if (s.find("operating system version") != std::string::npos ||
+ s.find("subsystem version") != std::string::npos) {
+ s.erase(s.begin(), std::find_if(s.begin(), s.end(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))));
+ std::cout << s << std::endl;
+ }
+#endif
+ }
+
+ unlink("qbs-test-dumpbin.txt");
+#else
+ std::cout << "WINVER is not defined" << std::endl;
+#endif
+#endif
+
+ return 0;
+}
diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/main.mm b/tests/auto/blackbox/testdata/minimumSystemVersion/main.mm
new file mode 100644
index 000000000..cd5a8ec75
--- /dev/null
+++ b/tests/auto/blackbox/testdata/minimumSystemVersion/main.mm
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <iostream>
+#import <Foundation/Foundation.h>
+
+int main()
+{
+ // This gets set by -mmacosx-version-min. If left undefined, defaults to the current OS version.
+ std::cout << "__MAC_OS_X_VERSION_MIN_REQUIRED="
+ << __MAC_OS_X_VERSION_MIN_REQUIRED << std::endl;
+
+ bool print = false;
+ NSTask *task = [[NSTask alloc] init];
+ [task setLaunchPath:@"/usr/bin/otool"];
+ [task setArguments:[NSArray arrayWithObjects:@"-l", [[[NSProcessInfo processInfo] arguments] firstObject], nil]];
+ NSPipe *pipe = [NSPipe pipe];
+ [task setStandardOutput:pipe];
+ [task launch];
+ NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];
+ [task waitUntilExit];
+ NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSString *line;
+ NSEnumerator *enumerator = [[result componentsSeparatedByString:@"\n"] objectEnumerator];
+ while ((line = [enumerator nextObject]) != nil) {
+ if ([line rangeOfString:@"LC_VERSION_MIN_MACOSX"].location != NSNotFound)
+ print = true;
+
+ if (print && [line rangeOfString:@"version"].location != NSNotFound) {
+ std::cout << [[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] UTF8String] << std::endl;
+ print = false;
+ }
+ }
+
+}
diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/specific.qbs b/tests/auto/blackbox/testdata/minimumSystemVersion/specific.qbs
new file mode 100644
index 000000000..0d033b394
--- /dev/null
+++ b/tests/auto/blackbox/testdata/minimumSystemVersion/specific.qbs
@@ -0,0 +1,26 @@
+import qbs
+import qbs.Utilities
+
+// a specific version of the operating systems is specified
+// when the application is run its output should confirm
+// that the given values took effect
+CppApplication {
+ condition: qbs.targetOS.contains("windows") || qbs.targetOS.contains("macos")
+ files: [qbs.targetOS.contains("darwin") ? "main.mm" : "main.cpp"]
+ consoleApplication: true
+
+ Properties {
+ condition: qbs.targetOS.contains("windows")
+ cpp.minimumWindowsVersion: "6.0"
+ cpp.defines: [
+ "QBS_WINVER=0x600",
+ "TOOLCHAIN_INSTALL_PATH=" + Utilities.cStringQuote(cpp.toolchainInstallPath)
+ ]
+ }
+
+ Properties {
+ condition: qbs.targetOS.contains("macos")
+ cpp.frameworks: "Foundation"
+ cpp.minimumMacosVersion: "10.6"
+ }
+}
diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/unspecified-forced.qbs b/tests/auto/blackbox/testdata/minimumSystemVersion/unspecified-forced.qbs
new file mode 100644
index 000000000..2285e20ba
--- /dev/null
+++ b/tests/auto/blackbox/testdata/minimumSystemVersion/unspecified-forced.qbs
@@ -0,0 +1,23 @@
+import qbs
+import qbs.Utilities
+
+// no minimum versions are specified, and explicitly set to undefined in
+// case the profile has set it
+CppApplication {
+ files: [qbs.targetOS.contains("darwin") ? "main.mm" : "main.cpp"]
+ consoleApplication: true
+ cpp.minimumWindowsVersion: undefined
+ cpp.minimumMacosVersion: undefined
+ cpp.minimumIosVersion: undefined
+ cpp.minimumAndroidVersion: undefined
+
+ Properties {
+ condition: qbs.targetOS.contains("windows")
+ cpp.defines: ["TOOLCHAIN_INSTALL_PATH=" + Utilities.cStringQuote(cpp.toolchainInstallPath)]
+ }
+
+ Properties {
+ condition: qbs.targetOS.contains("darwin")
+ cpp.frameworks: "Foundation"
+ }
+}
diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/unspecified.qbs b/tests/auto/blackbox/testdata/minimumSystemVersion/unspecified.qbs
new file mode 100644
index 000000000..ac0a7abdc
--- /dev/null
+++ b/tests/auto/blackbox/testdata/minimumSystemVersion/unspecified.qbs
@@ -0,0 +1,18 @@
+import qbs
+import qbs.Utilities
+
+// no minimum versions are specified so the profile defaults will be used
+CppApplication {
+ files: [qbs.targetOS.contains("darwin") ? "main.mm" : "main.cpp"]
+ consoleApplication: true
+
+ Properties {
+ condition: qbs.targetOS.contains("windows")
+ cpp.defines: ["TOOLCHAIN_INSTALL_PATH=" + Utilities.cStringQuote(cpp.toolchainInstallPath)]
+ }
+
+ Properties {
+ condition: qbs.targetOS.contains("darwin")
+ cpp.frameworks: "Foundation"
+ }
+}
diff --git a/tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs b/tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs
index 7ff4cf28b..827eda64e 100644
--- a/tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs
+++ b/tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs
@@ -3,8 +3,10 @@ import qbs
CppApplication {
consoleApplication: true
Depends { name: "themodule" }
+ cpp.includePaths: ["subdir"]
files: ["main.cpp"]
Group {
+ prefix: "subdir/"
cpp.defines: ["REQUIRED_FOR_FILE1", "BREAKS_FILE2"]
fileTags: ["cpp"]
diff --git a/tests/auto/blackbox/testdata/nested-groups/file1.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/file1.cpp
index 95255e4e1..95255e4e1 100644
--- a/tests/auto/blackbox/testdata/nested-groups/file1.cpp
+++ b/tests/auto/blackbox/testdata/nested-groups/subdir/file1.cpp
diff --git a/tests/auto/blackbox/testdata/nested-groups/file1.h b/tests/auto/blackbox/testdata/nested-groups/subdir/file1.h
index d52b9f057..d52b9f057 100644
--- a/tests/auto/blackbox/testdata/nested-groups/file1.h
+++ b/tests/auto/blackbox/testdata/nested-groups/subdir/file1.h
diff --git a/tests/auto/blackbox/testdata/nested-groups/file2.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/file2.cpp
index 87333eabb..87333eabb 100644
--- a/tests/auto/blackbox/testdata/nested-groups/file2.cpp
+++ b/tests/auto/blackbox/testdata/nested-groups/subdir/file2.cpp
diff --git a/tests/auto/blackbox/testdata/nested-groups/file2.h b/tests/auto/blackbox/testdata/nested-groups/subdir/file2.h
index 97e76d0e8..97e76d0e8 100644
--- a/tests/auto/blackbox/testdata/nested-groups/file2.h
+++ b/tests/auto/blackbox/testdata/nested-groups/subdir/file2.h
diff --git a/tests/auto/blackbox/testdata/nested-groups/main2.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/main2.cpp
index e14f806b0..e14f806b0 100644
--- a/tests/auto/blackbox/testdata/nested-groups/main2.cpp
+++ b/tests/auto/blackbox/testdata/nested-groups/subdir/main2.cpp
diff --git a/tests/auto/blackbox/testdata/nested-groups/main3.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/main3.cpp
index e14f806b0..e14f806b0 100644
--- a/tests/auto/blackbox/testdata/nested-groups/main3.cpp
+++ b/tests/auto/blackbox/testdata/nested-groups/subdir/main3.cpp
diff --git a/tests/auto/blackbox/testdata/nested-groups/other.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/other.cpp
index c18f141ef..c18f141ef 100644
--- a/tests/auto/blackbox/testdata/nested-groups/other.cpp
+++ b/tests/auto/blackbox/testdata/nested-groups/subdir/other.cpp
diff --git a/tests/auto/blackbox/testdata/nested-groups/other.h b/tests/auto/blackbox/testdata/nested-groups/subdir/other.h
index 4c04b3ed9..4c04b3ed9 100644
--- a/tests/auto/blackbox/testdata/nested-groups/other.h
+++ b/tests/auto/blackbox/testdata/nested-groups/subdir/other.h
diff --git a/tests/auto/blackbox/testdata/no-profile/no-profile.qbs b/tests/auto/blackbox/testdata/no-profile/no-profile.qbs
new file mode 100644
index 000000000..cc251c6ab
--- /dev/null
+++ b/tests/auto/blackbox/testdata/no-profile/no-profile.qbs
@@ -0,0 +1,5 @@
+import qbs
+
+Product {
+ property bool dummy: { console.info("profile: " + project.profile); }
+}
diff --git a/tests/auto/blackbox/testdata/system-include-paths/main.cpp b/tests/auto/blackbox/testdata/system-include-paths/main.cpp
new file mode 100644
index 000000000..e449173d3
--- /dev/null
+++ b/tests/auto/blackbox/testdata/system-include-paths/main.cpp
@@ -0,0 +1,8 @@
+#include <stdio.h>
+#include <gagagugu.h>
+
+int main()
+{
+ printStuff();
+ return 0;
+}
diff --git a/tests/auto/blackbox/testdata/system-include-paths/subdir/gagagugu.h b/tests/auto/blackbox/testdata/system-include-paths/subdir/gagagugu.h
new file mode 100644
index 000000000..b951d8855
--- /dev/null
+++ b/tests/auto/blackbox/testdata/system-include-paths/subdir/gagagugu.h
@@ -0,0 +1,4 @@
+void printStuff()
+{
+ puts("alalalalonglonglilonglonglong");
+}
diff --git a/tests/auto/blackbox/testdata/system-include-paths/system-include-paths.qbs b/tests/auto/blackbox/testdata/system-include-paths/system-include-paths.qbs
new file mode 100644
index 000000000..081d40202
--- /dev/null
+++ b/tests/auto/blackbox/testdata/system-include-paths/system-include-paths.qbs
@@ -0,0 +1,6 @@
+import qbs
+
+CppApplication {
+ files: ["main.cpp"]
+ cpp.systemIncludePaths: ["subdir"]
+}
diff --git a/tests/auto/blackbox/testdata/variant-suffix/lib.cpp b/tests/auto/blackbox/testdata/variant-suffix/lib.cpp
new file mode 100644
index 000000000..56757a701
--- /dev/null
+++ b/tests/auto/blackbox/testdata/variant-suffix/lib.cpp
@@ -0,0 +1 @@
+void f() {}
diff --git a/tests/auto/blackbox/testdata/variant-suffix/variant-suffix.qbs b/tests/auto/blackbox/testdata/variant-suffix/variant-suffix.qbs
new file mode 100644
index 000000000..b919f84eb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/variant-suffix/variant-suffix.qbs
@@ -0,0 +1,43 @@
+import qbs
+
+StaticLibrary {
+ name: "l"
+
+ Depends { condition: qbs.targetOS.contains("darwin"); name: "bundle" }
+ Properties { condition: qbs.targetOS.contains("darwin"); bundle.isBundle: false }
+
+ aggregate: false
+ property string variantSuffix
+ property bool multiplex: false
+ Properties {
+ condition: multiplex
+ qbs.buildVariants: ["debug", "release"]
+ multiplexByQbsProperties: ["buildVariants"]
+ }
+ Properties {
+ condition: variantSuffix !== undefined
+ cpp.variantSuffix: variantSuffix
+ }
+ cpp.variantSuffix: original
+ cpp.staticLibraryPrefix: "lib"
+ cpp.staticLibrarySuffix: ".ext"
+
+ Group {
+ fileTagsFilter: ["staticlibrary"]
+ qbs.install: true
+ qbs.installDir: "lib"
+ }
+
+ Depends { name: "cpp" }
+
+ files: ["lib.cpp"]
+
+ Probe {
+ id: targetOSProbe
+ property stringList targetOS: qbs.targetOS
+ configure: {
+ console.info("is Windows: " + targetOS.contains("windows"));
+ console.info("is Apple: " + targetOS.contains("darwin"));
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/vcs/main.cpp b/tests/auto/blackbox/testdata/vcs/main.cpp
new file mode 100644
index 000000000..0a5da7510
--- /dev/null
+++ b/tests/auto/blackbox/testdata/vcs/main.cpp
@@ -0,0 +1,7 @@
+#include <vcs-repo-state.h>
+#include <iostream>
+
+int main()
+{
+ std::cout << "I was built from " << VCS_REPO_STATE << std::endl;
+}
diff --git a/tests/auto/blackbox/testdata/vcs/vcstest.qbs b/tests/auto/blackbox/testdata/vcs/vcstest.qbs
new file mode 100644
index 000000000..39fc25cd4
--- /dev/null
+++ b/tests/auto/blackbox/testdata/vcs/vcstest.qbs
@@ -0,0 +1,6 @@
+import qbs
+
+CppApplication {
+ Depends { name: "vcs" }
+ files: ["main.cpp"]
+}
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index 176f20816..1ebcfe60e 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -47,6 +47,7 @@
#include <functional>
#include <regex>
+#include <utility>
#define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir)
@@ -194,6 +195,13 @@ void TestBlackbox::suspiciousCalls_data()
QTest::newRow("File.directoryEntries() in command") << "direntries-command.qbs" << QByteArray();
}
+void TestBlackbox::systemIncludePaths()
+{
+ const QString projectDir = testDataDir + "/system-include-paths";
+ QDir::setCurrent(projectDir);
+ QCOMPARE(runQbs(), 0);
+}
+
void TestBlackbox::tar()
{
if (HostOsInfo::hostOs() == HostOsInfo::HostOsWindows)
@@ -284,6 +292,17 @@ TestBlackbox::TestBlackbox() : TestBlackboxBase (SRCDIR "/testdata", "blackbox")
{
}
+void TestBlackbox::addFileTagToGeneratedArtifact()
+{
+ QDir::setCurrent(testDataDir + "/add-filetag-to-generated-artifact");
+ QCOMPARE(runQbs(), 0);
+ QVERIFY2(m_qbsStdout.contains("compressing my_app"), m_qbsStdout.constData());
+ const QString compressedAppFilePath
+ = relativeProductBuildDir("my_compressed_app") + '/'
+ + qbs::Internal::HostOsInfo::appendExecutableSuffix("compressed-my_app");
+ QVERIFY(regularFileExists(compressedAppFilePath));
+}
+
void TestBlackbox::alwaysRun()
{
QFETCH(QString, projectFile);
@@ -330,98 +349,98 @@ void TestBlackbox::artifactScanning()
QbsRunParameters params(QStringList("-vv"));
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 1);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 1);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 1);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 1);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 1);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 1);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 1);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
WAIT_FOR_NEW_TIMESTAMP();
touch("p1.cpp");
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 1);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
WAIT_FOR_NEW_TIMESTAMP();
touch("p2.cpp");
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 1);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
WAIT_FOR_NEW_TIMESTAMP();
touch("p3.cpp");
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 1);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
WAIT_FOR_NEW_TIMESTAMP();
touch("shared.h");
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 1);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
WAIT_FOR_NEW_TIMESTAMP();
touch("external.h");
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 1);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
WAIT_FOR_NEW_TIMESTAMP();
touch("subdir/external2.h");
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 1);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
WAIT_FOR_NEW_TIMESTAMP();
touch("external-indirect.h");
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 1);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
WAIT_FOR_NEW_TIMESTAMP();
touch("p1.cpp");
@@ -430,14 +449,14 @@ void TestBlackbox::artifactScanning()
QCOMPARE(runQbs(params), 0);
params.command = "build";
QCOMPARE(runQbs(params), 0);
- QCOMPARE(m_qbsStderr.count("scanning p1.cpp"), 1);
- QCOMPARE(m_qbsStderr.count("scanning p2.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning p3.cpp"), 0);
- QCOMPARE(m_qbsStderr.count("scanning shared.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external2.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning external-indirect.h"), 0);
- QCOMPARE(m_qbsStderr.count("scanning iostream"), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 1);
+ QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
+ QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 1);
}
void TestBlackbox::buildDirectories()
@@ -677,18 +696,15 @@ void TestBlackbox::deprecatedProperty()
QbsRunParameters params(QStringList("-q"));
params.expectFailure = true;
QVERIFY(runQbs(params) != 0);
+ m_qbsStderr = QDir::fromNativeSeparators(QString::fromLocal8Bit(m_qbsStderr)).toLocal8Bit();
QVERIFY2(m_qbsStderr.contains("deprecated-property.qbs:6:24 The property 'oldProp' is "
"deprecated and will be removed in Qbs 99.9.0."), m_qbsStderr.constData());
QVERIFY2(m_qbsStderr.contains("deprecated-property.qbs:7:28 The property 'veryOldProp' can no "
"longer be used. It was removed in Qbs 1.3.0."), m_qbsStderr.constData());
-
-
- // TODO: Uncomment in 1.10
-// QVERIFY2(m_qbsStderr.contains("Property 'forgottenProp' was scheduled for removal in version "
-// "1.8.0, but is still present."), m_qbsStderr.constData());
-// QVERIFY2(m_qbsStderr.contains("themodule/m.qbs:22:5 Removal version for 'forgottenProp' "
-// "specified here."), m_qbsStderr.constData());
-
+ QVERIFY2(m_qbsStderr.contains("Property 'forgottenProp' was scheduled for removal in version "
+ "1.8.0, but is still present."), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.contains("themodule/m.qbs:22:5 Removal version for 'forgottenProp' "
+ "specified here."), m_qbsStderr.constData());
QVERIFY2(m_qbsStderr.count("Use newProp instead.") == 2, m_qbsStderr.constData());
QVERIFY2(m_qbsStderr.count("is deprecated") == 1, m_qbsStderr.constData());
QVERIFY2(m_qbsStderr.count("was removed") == 1, m_qbsStderr.constData());
@@ -763,6 +779,46 @@ void TestBlackbox::disappearedProfile()
QVERIFY2(m_qbsStderr.contains("profile"), m_qbsStderr.constData());
}
+void TestBlackbox::discardUnusedData()
+{
+ QDir::setCurrent(testDataDir + "/discard-unused-data");
+ rmDirR(relativeBuildDir());
+ QFETCH(QString, discardString);
+ QFETCH(bool, symbolPresent);
+ QbsRunParameters params;
+ if (!discardString.isEmpty())
+ params.arguments << ("modules.cpp.discardUnusedData:" + discardString);
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains("is Darwin"), m_qbsStdout.constData());
+ const bool isDarwin = m_qbsStdout.contains("is Darwin: true");
+ const QString output = QString::fromLocal8Bit(m_qbsStdout);
+ QRegExp pattern(".*---(.*)---.*");
+ QVERIFY2(pattern.exactMatch(output), qPrintable(output));
+ QCOMPARE(pattern.captureCount(), 1);
+ const QString nmPath = pattern.capturedTexts().at(1);
+ if (!QFile::exists(nmPath))
+ QSKIP("Cannot check for symbol presence: No nm found.");
+ QProcess nm;
+ nm.start(nmPath, QStringList(QDir::currentPath() + '/' + relativeExecutableFilePath("app")));
+ QVERIFY(nm.waitForStarted());
+ QVERIFY(nm.waitForFinished());
+ const QByteArray nmOutput = nm.readAllStandardOutput();
+ QVERIFY2(nm.exitCode() == 0, nm.readAllStandardError().constData());
+ if (!symbolPresent && !isDarwin)
+ QSKIP("Unused symbol detection only supported on Darwin");
+ QVERIFY2(nmOutput.contains("unusedFunc") == symbolPresent, nmOutput.constData());
+}
+
+void TestBlackbox::discardUnusedData_data()
+{
+ QTest::addColumn<QString>("discardString");
+ QTest::addColumn<bool>("symbolPresent");
+
+ QTest::newRow("discard") << QString("true") << false;
+ QTest::newRow("don't discard") << QString("false") << true;
+ QTest::newRow("default") << QString() << true;
+}
+
void TestBlackbox::symlinkRemoval()
{
if (HostOsInfo::isWindowsHost())
@@ -789,6 +845,211 @@ void TestBlackbox::usingsAsSoleInputsNonMultiplexed()
QVERIFY(regularFileExists(p3BuildDir + "/custom2.out.plus"));
}
+void TestBlackbox::variantSuffix()
+{
+ QDir::setCurrent(testDataDir + "/variant-suffix");
+ QFETCH(bool, multiplex);
+ QFETCH(bool, expectFailure);
+ QFETCH(QString, variantSuffix);
+ QFETCH(QString, buildVariant);
+ QFETCH(QVariantMap, fileNames);
+ QbsRunParameters params;
+ params.command = "resolve";
+ params.arguments << "--force-probe-execution";
+ if (multiplex)
+ params.arguments << "products.l.multiplex:true";
+ else
+ params.arguments << ("modules.qbs.buildVariant:" + buildVariant);
+ if (!variantSuffix.isEmpty())
+ params.arguments << ("modules.cpp.variantSuffix:" + variantSuffix);
+ QCOMPARE(runQbs(params), 0);
+ const QString fileNameMapKey = m_qbsStdout.contains("is Windows: true")
+ ? "windows" : m_qbsStdout.contains("is Apple: true") ? "apple" : "unix";
+ if (variantSuffix.isEmpty() && multiplex && fileNameMapKey == "unix")
+ expectFailure = true;
+ params.command = "build";
+ params.expectFailure = expectFailure;
+ params.arguments = QStringList("--clean-install-root");
+ QCOMPARE(runQbs(params) == 0, !expectFailure);
+ if (expectFailure)
+ return;
+ const QStringList fileNameList = fileNames.value(fileNameMapKey).toStringList();
+ for (const QString &fileName : fileNameList) {
+ QFile libFile("default/install-root/lib/" + fileName);
+ QVERIFY2(libFile.exists(), qPrintable(libFile.fileName()));
+ }
+}
+
+void TestBlackbox::variantSuffix_data()
+{
+ QTest::addColumn<bool>("multiplex");
+ QTest::addColumn<bool>("expectFailure");
+ QTest::addColumn<QString>("variantSuffix");
+ QTest::addColumn<QString>("buildVariant");
+ QTest::addColumn<QVariantMap>("fileNames");
+
+ QTest::newRow("default suffix, debug") << false << false << QString() << QString("debug")
+ << QVariantMap({std::make_pair(QString("windows"), QStringList("libl.ext")),
+ std::make_pair(QString("apple"), QStringList("libl.ext")),
+ std::make_pair(QString("unix"), QStringList("libl.ext"))});
+ QTest::newRow("default suffix, release") << false << false << QString() << QString("release")
+ << QVariantMap({std::make_pair(QString("windows"), QStringList("libl.ext")),
+ std::make_pair(QString("apple"), QStringList("libl.ext")),
+ std::make_pair(QString("unix"), QStringList("libl.ext"))});
+ QTest::newRow("custom suffix, debug") << false << false << QString("blubb") << QString("debug")
+ << QVariantMap({std::make_pair(QString("windows"), QStringList("liblblubb.ext")),
+ std::make_pair(QString("apple"), QStringList("liblblubb.ext")),
+ std::make_pair(QString("unix"), QStringList("liblblubb.ext"))});
+ QTest::newRow("custom suffix, release") << false << false << QString("blubb")
+ << QString("release")
+ << QVariantMap({std::make_pair(QString("windows"), QStringList("liblblubb.ext")),
+ std::make_pair(QString("apple"), QStringList("liblblubb.ext")),
+ std::make_pair(QString("unix"), QStringList("liblblubb.ext"))});
+ QTest::newRow("default suffix, multiplex") << true << false << QString() << QString()
+ << QVariantMap({std::make_pair(QString("windows"),
+ QStringList({"libl.ext", "libld.ext"})),
+ std::make_pair(QString("apple"),
+ QStringList({"libl.ext", "libl_debug.ext"})),
+ std::make_pair(QString("unix"), QStringList())});
+ QTest::newRow("custom suffix, multiplex") << true << true << QString("blubb") << QString()
+ << QVariantMap({std::make_pair(QString("windows"), QStringList()),
+ std::make_pair(QString("apple"), QStringList()),
+ std::make_pair(QString("unix"), QStringList())});
+}
+
+static bool waitForProcessSuccess(QProcess &p)
+{
+ if (!p.waitForStarted() || !p.waitForFinished()) {
+ qDebug() << p.errorString();
+ return false;
+ }
+ if (p.exitCode() != 0) {
+ qDebug() << p.readAllStandardError();
+ return false;
+ }
+ return true;
+}
+
+void TestBlackbox::vcsGit()
+{
+ const QString gitFilePath = findExecutable(QStringList("git"));
+ if (gitFilePath.isEmpty())
+ QSKIP("git not found");
+
+ // Set up repo.
+ QTemporaryDir repoDir;
+ QVERIFY(repoDir.isValid());
+ ccp(testDataDir + "/vcs", repoDir.path());
+ QDir::setCurrent(repoDir.path());
+
+ QProcess git;
+ git.start(gitFilePath, QStringList("init"));
+ QVERIFY(waitForProcessSuccess(git));
+ git.start(gitFilePath, QStringList({"config", "user.name", "My Name"}));
+ QVERIFY(waitForProcessSuccess(git));
+ git.start(gitFilePath, QStringList({"config", "user.email", "me@example.com"}));
+ QVERIFY(waitForProcessSuccess(git));
+
+ // First qbs run fails: No git metadata yet.
+ QbsRunParameters failParams;
+ failParams.expectFailure = true;
+ QVERIFY(runQbs(failParams) != 0);
+
+ // Initial commit
+ git.start(gitFilePath, QStringList({"add", "main.cpp"}));
+ QVERIFY(waitForProcessSuccess(git));
+ git.start(gitFilePath, QStringList({"commit", "-m", "initial commit"}));
+ QVERIFY(waitForProcessSuccess(git));
+
+ // Initial run.
+ QCOMPARE(runQbs(), 0);
+ QVERIFY2(m_qbsStdout.contains("generating vcs-repo-state.h"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
+
+ // Run with no changes.
+ QCOMPARE(runQbs(), 0);
+ QVERIFY2(!m_qbsStdout.contains("generating vcs-repo-state.h"), m_qbsStderr.constData());
+ QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
+
+ // Run with changed source file.
+ WAIT_FOR_NEW_TIMESTAMP();
+ touch("main.cpp");
+ QCOMPARE(runQbs(), 0);
+ QVERIFY2(!m_qbsStdout.contains("generating vcs-repo-state.h"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
+
+ // Add new file to repo.
+ WAIT_FOR_NEW_TIMESTAMP();
+ touch("blubb.txt");
+ git.start(gitFilePath, QStringList({"add", "blubb.txt"}));
+ QVERIFY(waitForProcessSuccess(git));
+ git.start(gitFilePath, QStringList({"commit", "-m", "blubb!"}));
+ QVERIFY(waitForProcessSuccess(git));
+ QCOMPARE(runQbs(), 0);
+ QVERIFY2(m_qbsStdout.contains("generating vcs-repo-state.h"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
+}
+
+void TestBlackbox::vcsSubversion()
+{
+ const QString svnadminFilePath = findExecutable(QStringList("svnadmin"));
+ if (svnadminFilePath.isEmpty())
+ QSKIP("svnadmin not found");
+ const QString svnFilePath = findExecutable(QStringList("svn"));
+ if (svnFilePath.isEmpty())
+ QSKIP("svn not found");
+
+ // Set up repo.
+ QTemporaryDir repoDir;
+ QVERIFY(repoDir.isValid());
+ QProcess proc;
+ proc.setWorkingDirectory(repoDir.path());
+ proc.start(svnadminFilePath, QStringList({"create", "vcstest"}));
+ QVERIFY(waitForProcessSuccess(proc));
+ const QString projectUrl = "file://" + repoDir.path() + "/vcstest/trunk";
+ proc.start(svnFilePath, QStringList({"import", testDataDir + "/vcs", projectUrl, "-m",
+ "initial import"}));
+ QVERIFY(waitForProcessSuccess(proc));
+ QTemporaryDir checkoutDir;
+ QVERIFY(checkoutDir.isValid());
+ proc.setWorkingDirectory(checkoutDir.path());
+ proc.start(svnFilePath, QStringList({"co", projectUrl, "."}));
+ QVERIFY(waitForProcessSuccess(proc));
+
+ // Initial runs
+ QDir::setCurrent(checkoutDir.path());
+ QbsRunParameters failParams;
+ failParams.command = "run";
+ failParams.expectFailure = true;
+ const int retval = runQbs(failParams);
+ if (m_qbsStderr.contains("svn too old"))
+ QSKIP("svn too old");
+ QCOMPARE(retval, 0);
+ QVERIFY2(m_qbsStdout.contains("I was built from 1"), m_qbsStdout.constData());
+ QCOMPARE(runQbs(QbsRunParameters("run")), 0);
+ QVERIFY2(!m_qbsStdout.contains("generating vcs-repo-state.h"), m_qbsStderr.constData());
+ QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStdout.contains("I was built from 1"), m_qbsStdout.constData());
+
+ // Run with changed source file.
+ WAIT_FOR_NEW_TIMESTAMP();
+ touch("main.cpp");
+ QCOMPARE(runQbs(QbsRunParameters("run")), 0);
+ QVERIFY2(!m_qbsStdout.contains("generating vcs-repo-state.h"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStdout.contains("I was built from 1"), m_qbsStdout.constData());
+
+ // Add new file to repo.
+ WAIT_FOR_NEW_TIMESTAMP();
+ touch("blubb.txt");
+ proc.start(svnFilePath, QStringList({"add", "blubb.txt"}));
+ QVERIFY(waitForProcessSuccess(proc));
+ proc.start(svnFilePath, QStringList({"commit", "-m", "blubb!"}));
+ QVERIFY(waitForProcessSuccess(proc));
+ QCOMPARE(runQbs(QbsRunParameters("run")), 0);
+ QVERIFY2(m_qbsStdout.contains("I was built from 2"), m_qbsStdout.constData());
+}
+
void TestBlackbox::versionCheck()
{
QDir::setCurrent(testDataDir + "/versioncheck");
@@ -934,6 +1195,11 @@ void TestBlackbox::clean()
QDir::setCurrent(testDataDir + "/clean");
+ // Can't clean without a build graph.
+ QbsRunParameters failParams("clean");
+ failParams.expectFailure = true;
+ QVERIFY(runQbs(failParams) != 0);
+
// Default behavior: Remove all.
QCOMPARE(runQbs(), 0);
QVERIFY(regularFileExists(appObjectFilePath));
@@ -1043,6 +1309,15 @@ void TestBlackbox::conditionalFileTagger()
QVERIFY(m_qbsStdout.contains("compiling"));
}
+void TestBlackbox::configure()
+{
+ QDir::setCurrent(testDataDir + "/configure");
+ QbsRunParameters params;
+ params.command = "run";
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains("Configured at"), m_qbsStdout.constData());
+}
+
void TestBlackbox::conflictingArtifacts()
{
QDir::setCurrent(testDataDir + "/conflicting-artifacts");
@@ -1129,6 +1404,37 @@ void TestBlackbox::cxxLanguageVersion_data()
std::make_pair(QString("msvc-new"), QString("/std:"))});
}
+void TestBlackbox::cpuFeatures()
+{
+ QDir::setCurrent(testDataDir + "/cpu-features");
+ QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
+ const bool isX86 = m_qbsStdout.contains("is x86: true");
+ const bool isX64 = m_qbsStdout.contains("is x64: true");
+ if (!isX86 && !isX64) {
+ QVERIFY2(m_qbsStdout.contains("is x86: false") && m_qbsStdout.contains("is x64: false"),
+ m_qbsStdout.constData());
+ QSKIP("Not an x86 host");
+ }
+ const bool isGcc = m_qbsStdout.contains("is gcc: true");
+ const bool isMsvc = m_qbsStdout.contains("is msvc: true");
+ if (!isGcc && !isMsvc) {
+ QVERIFY2(m_qbsStdout.contains("is gcc: false") && m_qbsStdout.contains("is msvc: false"),
+ m_qbsStdout.constData());
+ QSKIP("Neither GCC nor MSVC");
+ }
+ QbsRunParameters params(QStringList{"--command-echo-mode", "command-line"});
+ params.expectFailure = true;
+ runQbs(params);
+ if (isGcc) {
+ QVERIFY2(m_qbsStdout.contains("-msse2") && m_qbsStdout.contains("-mavx")
+ && m_qbsStdout.contains("-mno-avx512f"), m_qbsStdout.constData());
+ } else {
+ QVERIFY2(m_qbsStdout.contains("/arch:AVX"), m_qbsStdout.constData());
+ QVERIFY2(!m_qbsStdout.contains("/arch:AVX2"), m_qbsStdout.constData());
+ QVERIFY2(m_qbsStdout.contains("/arch:SSE2") == isX86, m_qbsStdout.constData());
+ }
+}
+
void TestBlackbox::renameDependency()
{
QDir::setCurrent(testDataDir + "/renameDependency");
@@ -1673,16 +1979,14 @@ void TestBlackbox::reproducibleBuild()
QVERIFY2(object.open(QIODevice::ReadOnly), qPrintable(object.fileName()));
const QByteArray oldContents = object.readAll();
object.close();
- QbsRunParameters cleanParams = params;
- cleanParams.command = "clean";
- QCOMPARE(runQbs(cleanParams), 0);
+ QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
QVERIFY(!object.exists());
QCOMPARE(runQbs(params), 0);
QVERIFY(object.open(QIODevice::ReadOnly));
const QByteArray newContents = object.readAll();
QCOMPARE(oldContents == newContents, reproducible);
object.close();
- QCOMPARE(runQbs(cleanParams), 0);
+ QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
}
void TestBlackbox::reproducibleBuild_data()
@@ -1702,7 +2006,8 @@ void TestBlackbox::responseFiles()
QFile file("installed/response-file-content.txt");
QVERIFY(file.open(QIODevice::ReadOnly));
const QList<QByteArray> expected = QList<QByteArray>()
- << "foo" << qbs::Internal::shellQuote("with space").toUtf8() << "bar" << "";
+ << "foo" << qbs::Internal::shellQuote(QStringLiteral("with space")).toUtf8()
+ << "bar" << "";
QList<QByteArray> lines = file.readAll().split('\n');
for (int i = 0; i < lines.count(); ++i)
lines[i] = lines.at(i).trimmed();
@@ -2744,8 +3049,8 @@ void TestBlackbox::exportToOutsideSearchPath()
QbsRunParameters params;
params.expectFailure = true;
QVERIFY(runQbs(params) != 0);
- QVERIFY2(m_qbsStderr.contains("Module 'aModule' not found when setting up transitive "
- "dependencies for product 'theProduct'"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.contains("Dependency 'aModule' not found for product 'theProduct'."),
+ m_qbsStderr.constData());
}
void TestBlackbox::externalLibs()
@@ -3003,6 +3308,13 @@ void TestBlackbox::commandFile()
QCOMPARE(runQbs(params), 0);
}
+void TestBlackbox::compilerDefinesByLanguage()
+{
+ QDir::setCurrent(testDataDir + "/compilerDefinesByLanguage");
+ QbsRunParameters params(QStringList { "-f", "compilerDefinesByLanguage.qbs" });
+ QCOMPARE(runQbs(params), 0);
+}
+
void TestBlackbox::jsExtensionsFile()
{
QDir::setCurrent(testDataDir + "/jsextensions-file");
@@ -3027,9 +3339,10 @@ void TestBlackbox::jsExtensionsFileInfo()
QVERIFY(output.exists());
QVERIFY(output.open(QIODevice::ReadOnly));
const QList<QByteArray> lines = output.readAll().trimmed().split('\n');
- QCOMPARE(lines.count(), 24);
+ QCOMPARE(lines.count(), 25);
int i = 0;
QCOMPARE(lines.at(i++).trimmed().constData(), "blubb");
+ QCOMPARE(lines.at(i++).trimmed().constData(), "/usr/bin");
QCOMPARE(lines.at(i++).trimmed().constData(), "blubb.tar");
QCOMPARE(lines.at(i++).trimmed().constData(), "blubb.tar.gz");
QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp/blubb.tar.gz");
@@ -3127,12 +3440,13 @@ void TestBlackbox::jsExtensionsTextFile()
QVERIFY(file2.exists());
QVERIFY(file2.open(QIODevice::ReadOnly));
const QList<QByteArray> lines = file2.readAll().trimmed().split('\n');
- QCOMPARE(lines.count(), 5);
+ QCOMPARE(lines.count(), 6);
QCOMPARE(lines.at(0).trimmed().constData(), "false");
QCOMPARE(lines.at(1).trimmed().constData(), "First line.");
QCOMPARE(lines.at(2).trimmed().constData(), "Second line.");
QCOMPARE(lines.at(3).trimmed().constData(), "Third line.");
- QCOMPARE(lines.at(4).trimmed().constData(), "true");
+ QCOMPARE(lines.at(4).trimmed().constData(), qPrintable(QDir::currentPath() + "/file1.txt"));
+ QCOMPARE(lines.at(5).trimmed().constData(), "true");
}
void TestBlackbox::ld()
@@ -3296,6 +3610,20 @@ void TestBlackbox::linkerScripts()
QVERIFY2(nmOutput.contains("TEST_SYMBOL2"), nmOutput.constData());
}
+void TestBlackbox::listProducts()
+{
+ QDir::setCurrent(testDataDir + "/list-products");
+ QCOMPARE(runQbs(QbsRunParameters("list-products")), 0);
+ m_qbsStdout.replace("\r\n", "\n");
+ QVERIFY2(m_qbsStdout.contains(
+ "a\n"
+ "b {\"architecture\":\"mips\",\"buildVariant\":\"debug\"}\n"
+ "b {\"architecture\":\"mips\",\"buildVariant\":\"release\"}\n"
+ "b {\"architecture\":\"vax\",\"buildVariant\":\"debug\"}\n"
+ "b {\"architecture\":\"vax\",\"buildVariant\":\"release\"}\n"
+ "c\n"), m_qbsStdout.constData());
+}
+
void TestBlackbox::listPropertiesWithOuter()
{
QDir::setCurrent(testDataDir + "/list-properties-with-outer");
@@ -3391,6 +3719,15 @@ void TestBlackbox::newOutputArtifact()
QVERIFY(regularFileExists(the100thArtifact));
}
+void TestBlackbox::noProfile()
+{
+ QDir::setCurrent(testDataDir + "/no-profile");
+ QbsRunParameters params;
+ params.profile = "none";
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains("profile: none"), m_qbsStdout.constData());
+}
+
void TestBlackbox::nonBrokenFilesInBrokenProduct()
{
QDir::setCurrent(testDataDir + "/non-broken-files-in-broken-product");
@@ -4398,6 +4735,15 @@ void TestBlackbox::importsConflict()
QCOMPARE(runQbs(), 0);
}
+void TestBlackbox::includeLookup()
+{
+ QDir::setCurrent(testDataDir + "/includeLookup");
+ QbsRunParameters params;
+ params.command = "run";
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains("definition.."), m_qbsStdout.constData());
+}
+
static bool haveInnoSetup(const Profile &profile)
{
if (profile.value("innosetup.toolchainInstallPath").isValid())
@@ -4497,6 +4843,118 @@ void TestBlackbox::loadableModule()
QVERIFY2(m_qbsStdout.contains("foo = 42"), m_qbsStdout.constData());
}
+void TestBlackbox::localDeployment()
+{
+ QDir::setCurrent(testDataDir + "/localDeployment");
+ QFile main("main.cpp");
+ QVERIFY(main.open(QIODevice::ReadOnly));
+ QByteArray content = main.readAll();
+ content.replace('\r', "");
+ QbsRunParameters params;
+ params.command = "run";
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains(content), m_qbsStdout.constData());
+}
+
+void TestBlackbox::minimumSystemVersion()
+{
+ rmDirR(relativeBuildDir());
+ QDir::setCurrent(testDataDir + "/minimumSystemVersion");
+ QFETCH(QString, file);
+ QFETCH(QString, output);
+ QbsRunParameters params({ "-f", file + ".qbs" });
+ params.command = "run";
+ QCOMPARE(runQbs(params), 0);
+ if (!m_qbsStdout.contains(output.toUtf8())) {
+ qDebug() << "expected output:" << qPrintable(output);
+ qDebug() << "actual output:" << m_qbsStdout.constData();
+ }
+ QVERIFY(m_qbsStdout.contains(output.toUtf8()));
+}
+
+static qbs::Internal::Version fromMinimumDeploymentTargetValue(int v, bool isMacOS)
+{
+ if (isMacOS && v < 100000)
+ return qbs::Internal::Version(v / 100, v / 10 % 10, v % 10);
+ return qbs::Internal::Version(v / 10000, v / 100 % 100, v % 100);
+}
+
+static int toMinimumDeploymentTargetValue(const qbs::Internal::Version &v, bool isMacOS)
+{
+ if (isMacOS && v < qbs::Internal::Version(10, 10))
+ return (v.majorVersion() * 100) + (v.minorVersion() * 10) + v.patchLevel();
+ return (v.majorVersion() * 10000) + (v.minorVersion() * 100) + v.patchLevel();
+}
+
+static qbs::Internal::Version defaultClangMinimumDeploymentTarget()
+{
+ QProcess process;
+ process.start("/usr/bin/xcrun", {"-sdk", "macosx", "clang++",
+ "-target", "x86_64-apple-macosx-macho",
+ "-dM", "-E", "-x", "objective-c++", "/dev/null"});
+ if (waitForProcessSuccess(process)) {
+ const auto lines = process.readAllStandardOutput().split('\n');
+ for (const auto &line : lines) {
+ static const QByteArray prefix =
+ "#define __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ ";
+ if (line.startsWith(prefix)) {
+ bool ok = false;
+ int v = line.mid(prefix.size()).trimmed().toInt(&ok);
+ if (ok)
+ return fromMinimumDeploymentTargetValue(v, true);
+ break;
+ }
+ }
+ }
+
+ return qbs::Internal::Version();
+}
+
+void TestBlackbox::minimumSystemVersion_data()
+{
+ QTest::addColumn<QString>("file");
+ QTest::addColumn<QString>("output");
+
+ // Don't check for the full "version X.Y.Z\n" on macOS as some older versions of otool don't
+ // show the patch version. Instead, simply check for "version X.Y" with no trailing \n.
+
+ const QString unspecified = []() -> QString {
+ if (HostOsInfo::isMacosHost()) {
+ const auto v = defaultClangMinimumDeploymentTarget();
+ return "__MAC_OS_X_VERSION_MIN_REQUIRED="
+ + QString::number(toMinimumDeploymentTargetValue(v, true))
+ + "\nversion "
+ + QString::number(v.majorVersion()) + "." + QString::number(v.minorVersion());
+ }
+
+ if (HostOsInfo::isWindowsHost())
+ return "WINVER is not defined\n";
+
+ return "";
+ }();
+
+ const QString specific = []() -> QString {
+ if (HostOsInfo::isMacosHost())
+ return "__MAC_OS_X_VERSION_MIN_REQUIRED=1060\nversion 10.6\n";
+
+ if (HostOsInfo::isWindowsHost())
+ return "WINVER=1536\n6.00 operating system version\n6.00 subsystem version\n";
+
+ return "";
+ }();
+
+ QTest::newRow("unspecified") << "unspecified" << unspecified;
+ QTest::newRow("unspecified-forced") << "unspecified-forced" << unspecified;
+ if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacosHost())
+ QTest::newRow("specific") << "specific" << specific;
+ if (HostOsInfo::isWindowsHost())
+ QTest::newRow("fakewindows") << "fakewindows" << "WINVER=1283\n5.03 operating system "
+ "version\n5.03 subsystem version\n";
+ if (HostOsInfo::isMacosHost())
+ QTest::newRow("macappstore") << "macappstore" << "__MAC_OS_X_VERSION_MIN_REQUIRED=1068\n"
+ "version 10.6";
+}
+
void TestBlackbox::missingDependency()
{
QDir::setCurrent(testDataDir + "/missing-dependency");
diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h
index a56ac2e19..bbfdbdc61 100644
--- a/tests/auto/blackbox/tst_blackbox.h
+++ b/tests/auto/blackbox/tst_blackbox.h
@@ -39,6 +39,7 @@ public:
TestBlackbox();
private slots:
+ void addFileTagToGeneratedArtifact();
void alwaysRun();
void alwaysRun_data();
void artifactScanning();
@@ -59,16 +60,21 @@ private slots:
void cli();
void combinedSources();
void commandFile();
+ void compilerDefinesByLanguage();
void concurrentExecutor();
void conditionalExport();
void conditionalFileTagger();
+ void configure();
void conflictingArtifacts();
void cxxLanguageVersion();
void cxxLanguageVersion_data();
+ void cpuFeatures();
void dependenciesProperty();
void dependencyProfileMismatch();
void deprecatedProperty();
void disappearedProfile();
+ void discardUnusedData();
+ void discardUnusedData_data();
void dynamicMultiplexRule();
void dynamicProject();
void dynamicRuleOutputs();
@@ -88,6 +94,7 @@ private slots:
void importInPropertiesCondition();
void importingProduct();
void importsConflict();
+ void includeLookup();
void innoSetup();
void inputsFromDependencies();
void installable();
@@ -114,9 +121,13 @@ private slots:
void linkerMode();
void lexyacc();
void linkerScripts();
+ void listProducts();
void listPropertiesWithOuter();
void listPropertyOrder();
void loadableModule();
+ void localDeployment();
+ void minimumSystemVersion();
+ void minimumSystemVersion_data();
void missingDependency();
void missingProjectFile();
void missingOverridePrefix();
@@ -124,6 +135,7 @@ private slots:
void nestedGroups();
void nestedProperties();
void newOutputArtifact();
+ void noProfile();
void nodejs();
void nonBrokenFilesInBrokenProduct();
void nonDefaultProduct();
@@ -179,6 +191,7 @@ private slots:
void staticLibWithoutSources();
void suspiciousCalls();
void suspiciousCalls_data();
+ void systemIncludePaths();
void systemRunPaths();
void systemRunPaths_data();
void tar();
@@ -195,6 +208,10 @@ private slots:
void transitiveOptionalDependencies();
void typescript();
void usingsAsSoleInputsNonMultiplexed();
+ void variantSuffix();
+ void variantSuffix_data();
+ void vcsGit();
+ void vcsSubversion();
void versionCheck();
void versionCheck_data();
void versionScript();
diff --git a/tests/auto/blackbox/tst_blackboxandroid.cpp b/tests/auto/blackbox/tst_blackboxandroid.cpp
new file mode 100644
index 000000000..090f6c414
--- /dev/null
+++ b/tests/auto/blackbox/tst_blackboxandroid.cpp
@@ -0,0 +1,154 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "tst_blackboxandroid.h"
+
+#include "../shared.h"
+#include <tools/profile.h>
+#include <tools/qttools.h>
+
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qtemporarydir.h>
+
+using qbs::Profile;
+
+QMap<QString, QString> TestBlackboxAndroid::findAndroid(int *status, const QString &profile)
+{
+ QTemporaryDir temp;
+ QDir::setCurrent(testDataDir + "/find");
+ QbsRunParameters params = QStringList({"-f", "find-android.qbs", "qbs.architecture:x86"});
+ params.profile = profile;
+ params.buildDirectory = temp.path();
+ const int res = runQbs(params);
+ if (status)
+ *status = res;
+ QFile file(temp.path() + "/" + relativeProductBuildDir("find-android")
+ + "/android.json");
+ if (!file.open(QIODevice::ReadOnly))
+ return QMap<QString, QString> { };
+ const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
+ return QMap<QString, QString> {
+ {"sdk", QDir::fromNativeSeparators(tools["sdk"].toString())},
+ {"sdk-build-tools-dx", QDir::fromNativeSeparators(tools["sdk-build-tools-dx"].toString())},
+ {"ndk", QDir::fromNativeSeparators(tools["ndk"].toString())},
+ {"ndk-samples", QDir::fromNativeSeparators(tools["ndk-samples"].toString())},
+ };
+}
+
+TestBlackboxAndroid::TestBlackboxAndroid()
+ : TestBlackboxBase(SRCDIR "/testdata-android", "blackbox-android")
+{
+}
+
+void TestBlackboxAndroid::validateTestProfile()
+{
+ const SettingsPtr s = settings();
+ Profile p("qbs_autotests-android", s.get());
+ if (!p.exists())
+ QSKIP("No Android test profile");
+}
+
+void TestBlackboxAndroid::android()
+{
+ QFETCH(QString, projectDir);
+ QFETCH(QStringList, productNames);
+ QFETCH(QList<int>, apkFileCounts);
+
+ const SettingsPtr s = settings();
+ Profile p("qbs_autotests-android", s.get());
+ int status;
+ const auto androidPaths = findAndroid(&status, p.name());
+ QCOMPARE(status, 0);
+
+ const auto ndkPath = androidPaths["ndk"];
+ const auto ndkSamplesPath = androidPaths["ndk-samples"];
+ static const QStringList ndkSamplesDirs = QStringList() << "teapot" << "no-native";
+ if (!ndkPath.isEmpty() && !QFileInfo(ndkSamplesPath).isDir()
+ && ndkSamplesDirs.contains(projectDir))
+ QSKIP("NDK samples directory not present");
+
+ QDir::setCurrent(testDataDir + "/" + projectDir);
+ QbsRunParameters params(QStringList { "--command-echo-mode", "command-line",
+ "modules.Android.ndk.platform:android-21" });
+ params.profile = p.name();
+ QCOMPARE(runQbs(params), 0);
+ for (int i = 0; i < productNames.count(); ++i) {
+ const QString productName = productNames.at(i);
+ QVERIFY(m_qbsStdout.contains(productName.toLocal8Bit() + ".apk"));
+ const QString apkFilePath = relativeProductBuildDir(productName)
+ + '/' + productName + ".apk";
+ QVERIFY2(regularFileExists(apkFilePath), qPrintable(apkFilePath));
+ const QString jarFilePath = findExecutable(QStringList("jar"));
+ QVERIFY(!jarFilePath.isEmpty());
+ QProcess jar;
+ jar.start(jarFilePath, QStringList() << "-tf" << apkFilePath);
+ QVERIFY2(jar.waitForStarted(), qPrintable(jar.errorString()));
+ QVERIFY2(jar.waitForFinished(), qPrintable(jar.errorString()));
+ QVERIFY2(jar.exitCode() == 0, qPrintable(jar.readAllStandardError().constData()));
+ QCOMPARE(jar.readAllStandardOutput().trimmed().split('\n').count(), apkFileCounts.at(i));
+ }
+
+ if (projectDir == "multiple-libs-per-apk") {
+ const auto dxPath = androidPaths["sdk-build-tools-dx"];
+ QVERIFY(!dxPath.isEmpty());
+ const auto lines = m_qbsStdout.split('\n');
+ const auto it = std::find_if(lines.cbegin(), lines.cend(), [&](const QByteArray &line) {
+ return !line.isEmpty() && line.startsWith(dxPath.toUtf8());
+ });
+ QVERIFY2(it != lines.cend(), qPrintable(m_qbsStdout.constData()));
+ const auto line = *it;
+ QVERIFY2(line.contains("lib3.jar"), qPrintable(line.constData()));
+ QVERIFY2(!line.contains("lib4.jar"), qPrintable(line.constData()));
+ QVERIFY2(line.contains("lib5.jar"), qPrintable(line.constData()));
+ QVERIFY2(line.contains("lib6.jar"), qPrintable(line.constData()));
+ QVERIFY2(!line.contains("lib7.jar"), qPrintable(line.constData()));
+ QVERIFY2(line.contains("lib8.jar"), qPrintable(line.constData()));
+ }
+}
+
+void TestBlackboxAndroid::android_data()
+{
+ const SettingsPtr s = settings();
+ const Profile p("qbs_autotests-android", s.get());
+ const int archCount = p.value(QLatin1String("qbs.architectures")).toStringList().count();
+
+ QTest::addColumn<QString>("projectDir");
+ QTest::addColumn<QStringList>("productNames");
+ QTest::addColumn<QList<int>>("apkFileCounts");
+ QTest::newRow("teapot") << "teapot" << QStringList("com.sample.teapot")
+ << (QList<int>() << (13 + 3*archCount));
+ QTest::newRow("no native") << "no-native"
+ << QStringList("com.example.android.basicmediadecoder") << (QList<int>() << 22);
+ QTest::newRow("multiple libs") << "multiple-libs-per-apk" << QStringList("twolibs")
+ << (QList<int>() << (6 + 4 * archCount));
+ QTest::newRow("multiple apks") << "multiple-apks-per-project"
+ << (QStringList() << "twolibs1" << "twolibs2")
+ << QList<int>({6 + archCount * 3 + 2, 5 + archCount * 4});
+}
+
+QTEST_MAIN(TestBlackboxAndroid)
diff --git a/tests/manual/localDeployment/main.cpp b/tests/auto/blackbox/tst_blackboxandroid.h
index 57e96c3cc..3b095ee00 100644
--- a/tests/manual/localDeployment/main.cpp
+++ b/tests/auto/blackbox/tst_blackboxandroid.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qbs.
@@ -26,15 +26,27 @@
**
****************************************************************************/
-#include <QFile>
-#include <QCoreApplication>
-#include <QDebug>
+#ifndef TST_BLACKBOX_H
+#define TST_BLACKBOX_H
-int main(int argc, char **argv)
+#include "tst_blackboxbase.h"
+
+class TestBlackboxAndroid : public TestBlackboxBase
{
- QCoreApplication app(argc, argv);
- QFile f(app.applicationDirPath() + "/../share/main.cpp");
- f.open(QFile::ReadOnly);
- qDebug() << f.readAll();
-}
+ Q_OBJECT
+
+public:
+ TestBlackboxAndroid();
+
+protected:
+ void validateTestProfile() override;
+
+private slots:
+ void android();
+ void android_data();
+
+private:
+ QMap<QString, QString> findAndroid(int *status, const QString &profile);
+};
+#endif // TST_BLACKBOX_H
diff --git a/tests/auto/blackbox/tst_blackboxapple.cpp b/tests/auto/blackbox/tst_blackboxapple.cpp
index 1829d173e..98b06a6b1 100644
--- a/tests/auto/blackbox/tst_blackboxapple.cpp
+++ b/tests/auto/blackbox/tst_blackboxapple.cpp
@@ -100,11 +100,21 @@ void TestBlackboxApple::appleMultiConfig()
QVERIFY(QFileInfo2(defaultInstallRoot + "/multiapp.app/Contents/Info.plist").isRegularFile());
QVERIFY(QFileInfo2(defaultInstallRoot + "/multiapp.app/Contents/PkgInfo").isRegularFile());
- QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiapp.app/Contents/MacOS/fatmultiapp").isFileSymLink());
- QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiapp.app/Contents/MacOS/fatmultiapp_debug").isExecutable());
+ QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiapp.app/Contents/MacOS/fatmultiapp").isExecutable());
QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiapp.app/Contents/Info.plist").isRegularFile());
QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiapp.app/Contents/PkgInfo").isRegularFile());
+ QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/Contents/MacOS/"
+ "fatmultiappmultivariant").isFileSymLink());
+ QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/Contents/MacOS/"
+ "fatmultiappmultivariant_debug").isExecutable());
+ QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/Contents/MacOS/"
+ "fatmultiappmultivariant_profile").isExecutable());
+ QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/Contents/Info.plist")
+ .isRegularFile());
+ QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/Contents/PkgInfo")
+ .isRegularFile());
+
QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/multilib").isFileSymLink());
QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Resources").isDirSymLink());
QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Versions").isRegularDir());
@@ -227,9 +237,7 @@ void TestBlackboxApple::assetCatalog()
QCOMPARE(QDir(nib).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name), nibFiles);
QCOMPARE(QDir(storyboardc).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name), storyboardcFiles);
- QbsRunParameters params2 = params;
- params2.command = "clean";
- QCOMPARE(runQbs(params2), 0);
+ QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
QCOMPARE(QDir(nib).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name), QStringList());
QCOMPARE(QDir(storyboardc).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name), QStringList());
}
diff --git a/tests/auto/blackbox/tst_blackboxbase.cpp b/tests/auto/blackbox/tst_blackboxbase.cpp
index e484aab6a..88cbe1fce 100644
--- a/tests/auto/blackbox/tst_blackboxbase.cpp
+++ b/tests/auto/blackbox/tst_blackboxbase.cpp
@@ -82,8 +82,11 @@ int TestBlackboxBase::runQbs(const QbsRunParameters &params)
args.append(params.buildDirectory.isEmpty() ? QLatin1String(".") : params.buildDirectory);
}
args << params.arguments;
- if (!params.profile.isEmpty())
+ const bool commandImpliesResolve = params.command.isEmpty() || params.command == "resolve"
+ || params.command == "build" || params.command == "install" || params.command == "run";
+ if (!params.profile.isEmpty() && commandImpliesResolve) {
args.append(QLatin1String("profile:") + params.profile);
+ }
QProcess process;
process.setProcessEnvironment(params.environment);
process.start(qbsExecutableFilePath, args);
@@ -167,7 +170,7 @@ void TestBlackboxBase::initTestCase()
QVERIFY(regularFileExists(qbsExecutableFilePath));
const SettingsPtr s = settings();
- if (!s->profiles().contains(profileName()))
+ if (profileName() != "none" && !s->profiles().contains(profileName()))
QFAIL(QByteArray("The build profile '" + profileName().toLocal8Bit() +
"' could not be found. Please set it up on your machine."));
diff --git a/tests/auto/blackbox/tst_blackboxjava.cpp b/tests/auto/blackbox/tst_blackboxjava.cpp
index aee3cce3f..f3b999dc6 100644
--- a/tests/auto/blackbox/tst_blackboxjava.cpp
+++ b/tests/auto/blackbox/tst_blackboxjava.cpp
@@ -38,94 +38,10 @@
using qbs::Internal::HostOsInfo;
using qbs::Profile;
-QMap<QString, QString> TestBlackboxJava::findAndroid(int *status, const QString &profile)
-{
- QTemporaryDir temp;
- QDir::setCurrent(testDataDir + "/find");
- QbsRunParameters params = QStringList({"-f", "find-android.qbs", "qbs.architecture:x86"});
- params.profile = profile;
- params.buildDirectory = temp.path();
- const int res = runQbs(params);
- if (status)
- *status = res;
- QFile file(temp.path() + "/" + relativeProductBuildDir("find-android")
- + "/android.json");
- if (!file.open(QIODevice::ReadOnly))
- return QMap<QString, QString> { };
- const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
- return QMap<QString, QString> {
- {"sdk", QDir::fromNativeSeparators(tools["sdk"].toString())},
- {"ndk", QDir::fromNativeSeparators(tools["ndk"].toString())},
- {"ndk-samples", QDir::fromNativeSeparators(tools["ndk-samples"].toString())},
- };
-}
-
TestBlackboxJava::TestBlackboxJava() : TestBlackboxBase (SRCDIR "/testdata-java", "blackbox-java")
{
}
-void TestBlackboxJava::android()
-{
- QFETCH(QString, projectDir);
- QFETCH(QStringList, productNames);
- QFETCH(QList<int>, apkFileCounts);
-
- const SettingsPtr s = settings();
- Profile p("qbs_autotests-android", s.get());
- if (!p.exists())
- QSKIP("No Android test profile");
- int status;
- const auto androidPaths = findAndroid(&status, p.name());
- QCOMPARE(status, 0);
-
- const auto ndkPath = androidPaths["ndk"];
- const auto ndkSamplesPath = androidPaths["ndk-samples"];
- static const QStringList ndkSamplesDirs = QStringList() << "teapot" << "no-native";
- if (!ndkPath.isEmpty() && !QFileInfo(ndkSamplesPath).isDir()
- && ndkSamplesDirs.contains(projectDir))
- QSKIP("NDK samples directory not present");
-
- QDir::setCurrent(testDataDir + "/android/" + projectDir);
- QbsRunParameters params(QStringList() << "modules.Android.ndk.platform:android-21");
- params.profile = p.name();
- QCOMPARE(runQbs(params), 0);
- for (int i = 0; i < productNames.count(); ++i) {
- const QString productName = productNames.at(i);
- QVERIFY(m_qbsStdout.contains("Creating " + productName.toLocal8Bit() + ".apk"));
- const QString apkFilePath = relativeProductBuildDir(productName)
- + '/' + productName + ".apk";
- QVERIFY2(regularFileExists(apkFilePath), qPrintable(apkFilePath));
- const QString jarFilePath = findExecutable(QStringList("jar"));
- QVERIFY(!jarFilePath.isEmpty());
- QProcess jar;
- jar.start(jarFilePath, QStringList() << "-tf" << apkFilePath);
- QVERIFY2(jar.waitForStarted(), qPrintable(jar.errorString()));
- QVERIFY2(jar.waitForFinished(), qPrintable(jar.errorString()));
- QVERIFY2(jar.exitCode() == 0, qPrintable(jar.readAllStandardError().constData()));
- QCOMPARE(jar.readAllStandardOutput().trimmed().split('\n').count(), apkFileCounts.at(i));
- }
-}
-
-void TestBlackboxJava::android_data()
-{
- const SettingsPtr s = settings();
- const Profile p("qbs_autotests-android", s.get());
- const int archCount = p.value(QLatin1String("qbs.architectures")).toStringList().count();
-
- QTest::addColumn<QString>("projectDir");
- QTest::addColumn<QStringList>("productNames");
- QTest::addColumn<QList<int>>("apkFileCounts");
- QTest::newRow("teapot") << "teapot" << QStringList("com.sample.teapot")
- << (QList<int>() << (13 + 3*archCount));
- QTest::newRow("no native") << "no-native"
- << QStringList("com.example.android.basicmediadecoder") << (QList<int>() << 22);
- QTest::newRow("multiple libs") << "multiple-libs-per-apk" << QStringList("twolibs")
- << (QList<int>() << (6 + 4 * archCount));
- QTest::newRow("multiple apks") << "multiple-apks-per-project"
- << (QStringList() << "twolibs1" << "twolibs2")
- << QList<int>({6 + archCount * 3 + 2, 5 + archCount * 4});
-}
-
static QProcessEnvironment processEnvironmentWithCurrentDirectoryInLibraryPath()
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
diff --git a/tests/auto/blackbox/tst_blackboxjava.h b/tests/auto/blackbox/tst_blackboxjava.h
index f85259f46..68d8a7f80 100644
--- a/tests/auto/blackbox/tst_blackboxjava.h
+++ b/tests/auto/blackbox/tst_blackboxjava.h
@@ -39,15 +39,10 @@ public:
TestBlackboxJava();
private slots:
- void android();
- void android_data();
void java();
void javaDependencyTracking();
void javaDependencyTracking_data();
void javaDependencyTrackingInnerClass();
-
-private:
- QMap<QString, QString> findAndroid(int *status, const QString &profile);
};
#endif // TST_BLACKBOX_H
diff --git a/tests/auto/blackbox/tst_blackboxqt.cpp b/tests/auto/blackbox/tst_blackboxqt.cpp
index 8bf892fb0..f0ec35708 100644
--- a/tests/auto/blackbox/tst_blackboxqt.cpp
+++ b/tests/auto/blackbox/tst_blackboxqt.cpp
@@ -30,6 +30,7 @@
#include "../shared.h"
#include <tools/hostosinfo.h>
+#include <tools/preferences.h>
#include <tools/profile.h>
#include <QtCore/qjsondocument.h>
@@ -46,19 +47,21 @@ TestBlackboxQt::TestBlackboxQt() : TestBlackboxBase (SRCDIR "/testdata-qt", "bla
void TestBlackboxQt::validateTestProfile()
{
const SettingsPtr s = settings();
- if (!s->profiles().contains(profileName()))
+ if (profileName() != "none" && !s->profiles().contains(profileName()))
QFAIL(QByteArray("The build profile '" + profileName().toLocal8Bit() +
"' could not be found. Please set it up on your machine."));
- Profile buildProfile(profileName(), s.get());
const QStringList searchPaths
- = buildProfile.value(QLatin1String("preferences.qbsSearchPaths")).toStringList();
- if (searchPaths.isEmpty())
- QFAIL(QByteArray("The build profile '" + profileName().toLocal8Bit() +
- "' is not a valid Qt profile."));
- if (!QFileInfo(searchPaths.first()).isDir())
- QFAIL(QByteArray("The build profile '" + profileName().toLocal8Bit() +
- "' points to an invalid qbs search path."));
+ = qbs::Preferences(s.get(), profileName()).searchPaths(
+ QDir::cleanPath(QCoreApplication::applicationDirPath()));
+ for (const auto &searchPath : searchPaths) {
+ if (QFileInfo(searchPath + "/modules/Qt").isDir())
+ return;
+ }
+
+ QSKIP(QByteArray("The build profile '" + profileName().toLocal8Bit() +
+ "' is not a valid Qt profile and Qt was not found "
+ "in the global search paths."));
}
void TestBlackboxQt::autoQrc()
@@ -69,6 +72,20 @@ void TestBlackboxQt::autoQrc()
m_qbsStdout.constData());
}
+void TestBlackboxQt::cachedQml()
+{
+ QDir::setCurrent(testDataDir + "/cached-qml");
+ QCOMPARE(runQbs(), 0);
+ QString dataDir = relativeBuildDir() + "/install-root/data";
+ if (QFile::exists(dataDir + "/main.cpp")) {
+ // If C++ source files were installed then Qt.qmlcache is not available. See project file.
+ QSKIP("No QML cache files generated.");
+ }
+ QVERIFY(QFile::exists(dataDir + "/main.qmlc"));
+ QVERIFY(QFile::exists(dataDir + "/MainForm.ui.qmlc"));
+ QVERIFY(QFile::exists(dataDir + "/stuff.jsc"));
+}
+
void TestBlackboxQt::combinedMoc()
{
QDir::setCurrent(testDataDir + "/combined-moc");
@@ -173,6 +190,16 @@ void TestBlackboxQt::mocFlags()
QVERIFY(runQbs(params) != 0);
}
+void TestBlackboxQt::pkgconfig()
+{
+ QDir::setCurrent(testDataDir + "/pkgconfig");
+ QbsRunParameters params;
+ params.command = "run";
+ QCOMPARE(runQbs(params), 0);
+ if (m_qbsStdout.contains("Skip this test"))
+ QSKIP("pkgconfig or Qt not found");
+}
+
void TestBlackboxQt::pluginMetaData()
{
QDir::setCurrent(testDataDir + "/plugin-meta-data");
@@ -242,6 +269,31 @@ void TestBlackboxQt::qtScxml()
m_qbsStdout.constData());
}
+void TestBlackboxQt::removeMocHeaderFromFileList()
+{
+ QDir::setCurrent(testDataDir + "/remove-moc-header-from-file-list");
+ QCOMPARE(runQbs(), 0);
+ WAIT_FOR_NEW_TIMESTAMP();
+ QFile projectFile("remove-moc-header-from-file-list.qbs");
+ QVERIFY2(projectFile.open(QIODevice::ReadWrite), qPrintable(projectFile.errorString()));
+ QByteArray content = projectFile.readAll();
+ content.replace("\"file.h\"", "// \"file.h\"");
+ projectFile.resize(0);
+ projectFile.write(content);
+ projectFile.close();
+ QbsRunParameters params;
+ params.expectFailure = true;
+ QVERIFY(runQbs(params) != 0);
+ WAIT_FOR_NEW_TIMESTAMP();
+ QVERIFY2(projectFile.open(QIODevice::ReadWrite), qPrintable(projectFile.errorString()));
+ content = projectFile.readAll();
+ content.replace("// \"file.h\"", "\"file.h\"");
+ projectFile.resize(0);
+ projectFile.write(content);
+ projectFile.close();
+ QCOMPARE(runQbs(), 0);
+}
+
void TestBlackboxQt::staticQtPluginLinking()
{
diff --git a/tests/auto/blackbox/tst_blackboxqt.h b/tests/auto/blackbox/tst_blackboxqt.h
index 5b8ceaae4..1a9d83e1e 100644
--- a/tests/auto/blackbox/tst_blackboxqt.h
+++ b/tests/auto/blackbox/tst_blackboxqt.h
@@ -43,6 +43,7 @@ protected:
private slots:
void autoQrc();
+ void cachedQml();
void combinedMoc();
void createProject();
void dbusAdaptors();
@@ -50,11 +51,13 @@ private slots:
void lrelease();
void mixedBuildVariants();
void mocFlags();
+ void pkgconfig();
void pluginMetaData();
void qmlDebugging();
void qobjectInObjectiveCpp();
void qtKeywords();
void qtScxml();
+ void removeMocHeaderFromFileList();
void staticQtPluginLinking();
void trackAddMocInclude();
void track_qobject_change();
diff --git a/tests/auto/cmdlineparser/tst_cmdlineparser.cpp b/tests/auto/cmdlineparser/tst_cmdlineparser.cpp
index de8ecc0b6..3dbcb5cec 100644
--- a/tests/auto/cmdlineparser/tst_cmdlineparser.cpp
+++ b/tests/auto/cmdlineparser/tst_cmdlineparser.cpp
@@ -60,7 +60,6 @@ private slots:
args.append("-v");
args << "--products" << "blubb";
args << "--changed-files" << "foo,bar" << fileArgs;
- args << "--force";
args << "--check-timestamps";
args << "--check-outputs";
CommandLineParser parser;
@@ -71,7 +70,6 @@ private slots:
QCOMPARE(parser.products(), QStringList() << "blubb");
QCOMPARE(parser.buildOptions(QString()).changedFiles().count(), 2);
QVERIFY(parser.buildOptions(QString()).keepGoing());
- QVERIFY(parser.force());
QVERIFY(parser.forceTimestampCheck());
QVERIFY(parser.forceOutputCheck());
QVERIFY(!parser.logTime());
@@ -79,7 +77,6 @@ private slots:
QVERIFY(parser.parseCommandLine(QStringList() << "-vvvqqq" << fileArgs));
QCOMPARE(ConsoleLogger::instance().logSink()->logLevel(), defaultLogLevel());
- QVERIFY(!parser.force());
QVERIFY(parser.parseCommandLine(QStringList() << "-t" << fileArgs));
QVERIFY(parser.logTime());
diff --git a/tests/auto/language/testdata/additional-product-types.qbs b/tests/auto/language/testdata/additional-product-types.qbs
new file mode 100644
index 000000000..af510bed7
--- /dev/null
+++ b/tests/auto/language/testdata/additional-product-types.qbs
@@ -0,0 +1,14 @@
+import qbs
+
+Product {
+ name: "p"
+ type: ["tag1"]
+
+ Depends { name: "dummy" }
+ Depends { name: "dummy2" }
+
+ property bool hasTag1: type.contains("tag1")
+ property bool hasTag2: type.contains("tag2")
+ property bool hasTag3: type.contains("tag3")
+ property bool hasTag4: type.contains("tag4")
+}
diff --git a/tests/auto/language/testdata/broken-dependency-cycle1.qbs b/tests/auto/language/testdata/broken-dependency-cycle1.qbs
new file mode 100644
index 000000000..d23e5b459
--- /dev/null
+++ b/tests/auto/language/testdata/broken-dependency-cycle1.qbs
@@ -0,0 +1,20 @@
+import qbs
+
+Project {
+ Product {
+ name: "p1"
+ Export {
+ property bool c: true
+ Depends { name: "p2"; condition: c }
+ }
+ }
+ Product {
+ name: "p2"
+ Depends { name: "p1" }
+ p1.c: false
+ }
+ Product {
+ name: "p3"
+ Depends { name: "p1" }
+ }
+}
diff --git a/tests/auto/language/testdata/broken-dependency-cycle2.qbs b/tests/auto/language/testdata/broken-dependency-cycle2.qbs
new file mode 100644
index 000000000..f0c6e4f57
--- /dev/null
+++ b/tests/auto/language/testdata/broken-dependency-cycle2.qbs
@@ -0,0 +1,20 @@
+import qbs
+
+Project {
+ Product {
+ name: "p1"
+ Export {
+ property bool c: true
+ Depends { name: "p2"; condition: c }
+ }
+ }
+ Product {
+ name: "p3"
+ Depends { name: "p1" }
+ }
+ Product {
+ name: "p2"
+ Depends { name: "p1" }
+ p1.c: false
+ }
+}
diff --git a/tests/auto/language/testdata/conditionaldepends.qbs b/tests/auto/language/testdata/conditionaldepends.qbs
index 263237030..e61888c9d 100644
--- a/tests/auto/language/testdata/conditionaldepends.qbs
+++ b/tests/auto/language/testdata/conditionaldepends.qbs
@@ -60,6 +60,16 @@ Project {
}
Product {
+ name: "multilevel_module_props_overridden"
+ Depends { name: "dummy3" }
+ }
+
+ Product {
+ name: "multilevel2_module_props_true"
+ Depends { name: "dummy3_loader" }
+ }
+
+ Product {
name: "contradictory_conditions1"
Depends { condition: false; name: "dummy" }
Depends { condition: true; name: "dummy" } // this one wins
diff --git a/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/bar/modules/conflicting-instances/bar.qbs b/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/bar/modules/conflicting-instances/bar.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/bar/modules/conflicting-instances/bar.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/foo/modules/conflicting-instances/foo.qbs b/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/foo/modules/conflicting-instances/foo.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/foo/modules/conflicting-instances/foo.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/product.qbs b/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/product.qbs
new file mode 100644
index 000000000..28033b174
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/product.qbs
@@ -0,0 +1,5 @@
+import qbs
+
+Product {
+ Depends { name: "conflicting-instances" }
+}
diff --git a/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/project.qbs b/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/project.qbs
new file mode 100644
index 000000000..76ecc2b5a
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/conflicting-module-instances-in-search-paths/project.qbs
@@ -0,0 +1,6 @@
+import qbs
+
+Project {
+ qbsSearchPaths: ["./foo", "./bar"]
+ references: ["product.qbs"]
+}
diff --git a/tests/auto/language/testdata/erroneous/conflicting-module-instances.qbs b/tests/auto/language/testdata/erroneous/conflicting-module-instances.qbs
new file mode 100644
index 000000000..28033b174
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/conflicting-module-instances.qbs
@@ -0,0 +1,5 @@
+import qbs
+
+Product {
+ Depends { name: "conflicting-instances" }
+}
diff --git a/tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance1.qbs b/tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance1.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance1.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance2.qbs b/tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance2.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance2.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/language/testdata/exports.qbs b/tests/auto/language/testdata/exports.qbs
index 4c3a472c6..64ff6483d 100644
--- a/tests/auto/language/testdata/exports.qbs
+++ b/tests/auto/language/testdata/exports.qbs
@@ -115,4 +115,22 @@ Project {
name: "depender"
Depends { name: "dependency" }
}
+
+ Product {
+ name: "broken_cycle1"
+ Export {
+ property bool depend: true
+ Depends { name: "broken_cycle3"; condition: depend } }
+ }
+ Product {
+ name: "broken_cycle2"
+ Export {
+ Depends { name: "broken_cycle1" }
+ broken_cycle1.depend: false
+ }
+ }
+ Product {
+ name: "broken_cycle3"
+ Depends { name: "broken_cycle2" }
+ }
}
diff --git a/tests/auto/language/testdata/filetags.qbs b/tests/auto/language/testdata/filetags.qbs
index eb1cf57c6..b2a85f953 100644
--- a/tests/auto/language/testdata/filetags.qbs
+++ b/tests/auto/language/testdata/filetags.qbs
@@ -58,4 +58,24 @@ Project {
fileTags: ["zzz"]
}
}
+
+ Product {
+ name: "prioritized_filetagger"
+ files: ["main.cpp"]
+ FileTagger {
+ patterns: ["*.cpp"]
+ fileTags: ["cpp1"]
+ priority: 3
+ }
+ FileTagger {
+ patterns: ["*.cpp"]
+ fileTags: ["cpp2"]
+ priority: 3
+ }
+ FileTagger {
+ patterns: ["*.cpp"]
+ fileTags: ["ignored"]
+ priority: 2
+ }
+ }
}
diff --git a/tests/auto/language/testdata/modules/dummy/dummy.qbs b/tests/auto/language/testdata/modules/dummy/dummy.qbs
index 8a235555a..1b985880e 100644
--- a/tests/auto/language/testdata/modules/dummy/dummy.qbs
+++ b/tests/auto/language/testdata/modules/dummy/dummy.qbs
@@ -2,6 +2,9 @@ import "dummy_base.qbs" as DummyBase
DummyBase {
condition: true
+
+ additionalProductTypes: ["tag2"]
+
property stringList defines
property stringList cFlags
property stringList cxxFlags
diff --git a/tests/auto/language/testdata/modules/dummy2/dummy2.qbs b/tests/auto/language/testdata/modules/dummy2/dummy2.qbs
index f60c38a71..b4a223c19 100644
--- a/tests/auto/language/testdata/modules/dummy2/dummy2.qbs
+++ b/tests/auto/language/testdata/modules/dummy2/dummy2.qbs
@@ -1,6 +1,8 @@
import qbs 1.0
Module {
+ additionalProductTypes: ["tag3"]
+
property var defines
property var someTrueProp: true
property var someFalseProp: false
diff --git a/tests/auto/language/testdata/modules/dummy3_loader/dummy3_loader.qbs b/tests/auto/language/testdata/modules/dummy3_loader/dummy3_loader.qbs
new file mode 100644
index 000000000..988f86f11
--- /dev/null
+++ b/tests/auto/language/testdata/modules/dummy3_loader/dummy3_loader.qbs
@@ -0,0 +1,6 @@
+import qbs 1.0
+
+Module {
+ Depends { name: "dummy3" }
+ dummy3.loadDummy: true
+}
diff --git a/tests/auto/language/testdata/multiplexing-by-profile/p1.qbs b/tests/auto/language/testdata/multiplexing-by-profile/p1.qbs
new file mode 100644
index 000000000..207b5df61
--- /dev/null
+++ b/tests/auto/language/testdata/multiplexing-by-profile/p1.qbs
@@ -0,0 +1,17 @@
+import qbs
+
+Project {
+ Profile {
+ name: "theProfile"
+ qbs.architecture: "dummy"
+ }
+ Product {
+ name: "p1"
+ qbs.profiles: ["theProfile"]
+ }
+ Product {
+ name: "p2"
+ qbs.profiles: ["theProfile"]
+ Depends { name: "p1" }
+ }
+}
diff --git a/tests/auto/language/testdata/multiplexing-by-profile/p2.qbs b/tests/auto/language/testdata/multiplexing-by-profile/p2.qbs
new file mode 100644
index 000000000..13db0afd7
--- /dev/null
+++ b/tests/auto/language/testdata/multiplexing-by-profile/p2.qbs
@@ -0,0 +1,21 @@
+import qbs
+
+Project {
+ Profile {
+ name: "profile1"
+ qbs.architecture: "dummy"
+ }
+ Profile {
+ name: "profile2"
+ qbs.architecture: "blubb"
+ }
+ Product {
+ name: "p1"
+ qbs.profiles: ["profile1"]
+ }
+ Product {
+ name: "p2"
+ qbs.profiles: ["profile1", "profile2"]
+ Depends { name: "p1" }
+ }
+}
diff --git a/tests/auto/language/testdata/multiplexing-by-profile/p3.qbs b/tests/auto/language/testdata/multiplexing-by-profile/p3.qbs
new file mode 100644
index 000000000..4c01e7c46
--- /dev/null
+++ b/tests/auto/language/testdata/multiplexing-by-profile/p3.qbs
@@ -0,0 +1,29 @@
+import qbs
+
+Project {
+ Profile {
+ name: "profile1"
+ qbs.architecture: "dummy"
+ }
+ Profile {
+ name: "profile2"
+ qbs.architecture: "blubb"
+ }
+ Profile {
+ name: "profile3"
+ qbs.architecture: "hurz"
+ }
+ Profile {
+ name: "profile4"
+ qbs.architecture: "zonk"
+ }
+ Product {
+ name: "p1"
+ qbs.profiles: ["profile1", "profile2"]
+ Depends { name: "p2" }
+ }
+ Product {
+ name: "p2"
+ qbs.profiles: ["profile3", "profile4"]
+ }
+}
diff --git a/tests/auto/language/testdata/multiplexing-by-profile/p4.qbs b/tests/auto/language/testdata/multiplexing-by-profile/p4.qbs
new file mode 100644
index 000000000..84695e85d
--- /dev/null
+++ b/tests/auto/language/testdata/multiplexing-by-profile/p4.qbs
@@ -0,0 +1,21 @@
+import qbs
+
+Project {
+ Profile {
+ name: "profile1"
+ qbs.architecture: "dummy"
+ }
+ Profile {
+ name: "profile2"
+ qbs.architecture: "blubb"
+ }
+ Product {
+ name: "p1"
+ qbs.profiles: ["profile1"]
+ Depends { name: "p2"; profiles: ["profile1"] }
+ }
+ Product {
+ name: "p2"
+ qbs.profiles: ["profile1", "profile2"]
+ }
+}
diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp
index 19c67eda3..a5b211934 100644
--- a/tests/auto/language/tst_language.cpp
+++ b/tests/auto/language/tst_language.cpp
@@ -181,6 +181,28 @@ void TestLanguage::cleanupTestCase()
delete loader;
}
+void TestLanguage::additionalProductTypes()
+{
+ bool exceptionCaught = false;
+ try {
+ defaultParameters.setProjectFilePath(testProject("additional-product-types.qbs"));
+ project = loader->loadProject(defaultParameters);
+ QVERIFY(!!project);
+ const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
+ const ResolvedProductConstPtr product = products.value("p");
+ QVERIFY(!!product);
+ const QVariantMap cfg = product->productProperties;
+ QVERIFY(cfg.value("hasTag1").toBool());
+ QVERIFY(cfg.value("hasTag2").toBool());
+ QVERIFY(cfg.value("hasTag3").toBool());
+ QVERIFY(!cfg.value("hasTag4").toBool());
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
+ qDebug() << e.toString();
+ }
+ QCOMPARE(exceptionCaught, false);
+}
+
void TestLanguage::baseProperty()
{
bool exceptionCaught = false;
@@ -214,6 +236,25 @@ void TestLanguage::baseValidation()
}
}
+void TestLanguage::brokenDependencyCycle()
+{
+ qbs::SetupProjectParameters params = defaultParameters;
+ QFETCH(QString, projectFileName);
+ params.setProjectFilePath(testProject(qPrintable(projectFileName)));
+ try {
+ project = loader->loadProject(params);
+ } catch (const qbs::ErrorInfo &e) {
+ QVERIFY2(false, qPrintable(e.toString()));
+ }
+}
+
+void TestLanguage::brokenDependencyCycle_data()
+{
+ QTest::addColumn<QString>("projectFileName");
+ QTest::newRow("one order of products") << "broken-dependency-cycle1.qbs";
+ QTest::newRow("another order of products") << "broken-dependency-cycle2.qbs";
+}
+
void TestLanguage::buildConfigStringListSyntax()
{
bool exceptionCaught = false;
@@ -323,8 +364,11 @@ void TestLanguage::conditionalDepends()
ResolvedProductPtr product;
ResolvedModuleConstPtr dependency;
try {
- defaultParameters.setProjectFilePath(testProject("conditionaldepends.qbs"));
- project = loader->loadProject(defaultParameters);
+ SetupProjectParameters params = defaultParameters;
+ params.setProjectFilePath(testProject("conditionaldepends.qbs"));
+ params.setOverriddenValues({std::make_pair(QString("products."
+ "multilevel_module_props_overridden.dummy3.loadDummy"), true)});
+ project = loader->loadProject(params);
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
@@ -377,7 +421,6 @@ void TestLanguage::conditionalDepends()
dependency = findModuleByName(product, "dummy3");
QVERIFY(!!dependency);
dependency = findModuleByName(product, "dummy");
- QEXPECT_FAIL("", "This is broken. Sad!", Continue);
QVERIFY(!!dependency);
product = products.value("multilevel_module_props_false");
@@ -387,6 +430,22 @@ void TestLanguage::conditionalDepends()
dependency = findModuleByName(product, "dummy");
QCOMPARE(dependency, ResolvedModuleConstPtr());
+ product = products.value("multilevel_module_props_overridden");
+ QVERIFY(!!product);
+ dependency = findModuleByName(product, "dummy3");
+ QVERIFY(!!dependency);
+ dependency = findModuleByName(product, "dummy");
+ QVERIFY(!!dependency);
+
+ product = products.value("multilevel2_module_props_true");
+ QVERIFY(!!product);
+ dependency = findModuleByName(product, "dummy3_loader");
+ QVERIFY(!!dependency);
+ dependency = findModuleByName(product, "dummy3");
+ QVERIFY(!!dependency);
+ dependency = findModuleByName(product, "dummy");
+ QVERIFY(!!dependency);
+
product = products.value("contradictory_conditions1");
QVERIFY(!!product);
dependency = findModuleByName(product, "dummy");
@@ -686,6 +745,10 @@ void TestLanguage::erroneousFiles_data()
QTest::newRow("wrong-toplevel-item")
<< "wrong-toplevel-item.qbs:3:1.*The top-level item must be of type 'Project' or "
"'Product', but it is of type 'Artifact'.";
+ QTest::newRow("conflicting-module-instances")
+ << "There is more than one candidate for module 'conflicting-instances'.";
+ QTest::newRow("conflicting-module-instances-in-search-paths/project")
+ << "There is more than one candidate for module 'conflicting-instances'.";
QTest::newRow("module-depends-on-product")
<< "module-with-product-dependency.qbs:4:5.*Modules cannot depend on products.";
QTest::newRow("overwrite-inherited-readonly-property")
@@ -695,9 +758,9 @@ void TestLanguage::erroneousFiles_data()
<< "overwrite-readonly-module-property.qbs"
":5:30.*Cannot set read-only property 'readOnlyString'.";
QTest::newRow("mismatching-multiplex-dependency")
- << "mismatching-multiplex-dependency.qbs:9:5 Dependency from product 'b' to "
- "product 'a' not fulfilled.\nNo product 'a' found with a matching multiplex "
- "configuration:\n\tqbs.architecture: mips";
+ << "mismatching-multiplex-dependency.qbs:9:5 Dependency from product "
+ "'b \\{\"architecture\":\"mips\"\\}' to product 'a \\{\"architecture\":\"mips\"\\}'"
+ " not fulfilled.";
}
void TestLanguage::erroneousFiles()
@@ -727,7 +790,7 @@ void TestLanguage::exports()
TopLevelProjectPtr project = loader->loadProject(defaultParameters);
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
- QCOMPARE(products.count(), 19);
+ QCOMPARE(products.count(), 22);
ResolvedProductPtr product;
product = products.value("myapp");
QVERIFY(!!product);
@@ -821,6 +884,15 @@ void TestLanguage::exports()
QVERIFY2(m->name == QString("qbs") || m->name == QString("dependency"),
qPrintable(m->name));
}
+
+ product = products.value("broken_cycle3");
+ QVERIFY(!!product);
+ QCOMPARE(product->modules.count(), 3);
+ for (const ResolvedModuleConstPtr &m : qAsConst(product->modules)) {
+ QVERIFY2(m->name == QString("qbs") || m->name == QString("broken_cycle1")
+ || m->name == QString("broken_cycle2"),
+ qPrintable(m->name));
+ }
}
catch (const ErrorInfo &e) {
exceptionCaught = true;
@@ -1667,6 +1739,32 @@ void TestLanguage::modules()
QCOMPARE(product->productProperties.value("foo").toString(), expectedProductProperty);
}
+void TestLanguage::multiplexingByProfile()
+{
+ QFETCH(QString, projectFileName);
+ QFETCH(bool, successExpected);
+ SetupProjectParameters params = defaultParameters;
+ params.setProjectFilePath(testDataDir() + "/multiplexing-by-profile/" + projectFileName);
+ try {
+ params.setDryRun(true);
+ const TopLevelProjectPtr project = loader->loadProject(params);
+ QVERIFY(successExpected);
+ QVERIFY(!!project);
+ } catch (const ErrorInfo &e) {
+ QVERIFY2(!successExpected, qPrintable(e.toString()));
+ }
+}
+
+void TestLanguage::multiplexingByProfile_data()
+{
+ QTest::addColumn<QString>("projectFileName");
+ QTest::addColumn<bool>("successExpected");
+ QTest::newRow("same profile") << "p1.qbs" << true;
+ QTest::newRow("dependency on non-multiplexed") << "p2.qbs" << true;
+ QTest::newRow("dependency by non-multiplexed") << "p3.qbs" << false;
+ QTest::newRow("dependency by non-multiplexed with Depends.profile") << "p4.qbs" << true;
+}
+
void TestLanguage::nonRequiredProducts()
{
bool exceptionCaught = false;
@@ -2307,6 +2405,7 @@ void TestLanguage::fileTags_data()
QTest::newRow("set_file_tag_via_group") << 2 << (QStringList() << "c++");
QTest::newRow("override_file_tag_via_group") << 2 << (QStringList() << "c++");
QTest::newRow("add_file_tag_via_group") << 2 << (QStringList() << "cpp" << "zzz");
+ QTest::newRow("prioritized_filetagger") << 1 << (QStringList() << "cpp1" << "cpp2");
QTest::newRow("cleanup") << 0 << QStringList();
}
@@ -2550,10 +2649,9 @@ void TestLanguage::wildcards()
}
QVERIFY(!!group);
QCOMPARE(group->files.count(), 0);
- SourceWildCards::Ptr wildcards = group->wildcards;
- QVERIFY(!!wildcards);
+ QVERIFY(!!group->wildcards);
QStringList actualFilePaths;
- foreach (const SourceArtifactConstPtr &artifact, wildcards->files) {
+ foreach (const SourceArtifactConstPtr &artifact, group->wildcards->files) {
QString str = artifact->absoluteFilePath;
int idx = str.indexOf(m_wildcardsTestDirPath);
if (idx != -1)
diff --git a/tests/auto/language/tst_language.h b/tests/auto/language/tst_language.h
index 897a5cd3f..c9bf69644 100644
--- a/tests/auto/language/tst_language.h
+++ b/tests/auto/language/tst_language.h
@@ -76,8 +76,11 @@ private slots:
void initTestCase();
void cleanupTestCase();
+ void additionalProductTypes();
void baseProperty();
void baseValidation();
+ void brokenDependencyCycle();
+ void brokenDependencyCycle_data();
void buildConfigStringListSyntax();
void builtinFunctionInSearchPathsProperty();
void chainedProbes();
@@ -118,6 +121,8 @@ private slots:
void moduleScope();
void modules_data();
void modules();
+ void multiplexingByProfile();
+ void multiplexingByProfile_data();
void nonRequiredProducts();
void nonRequiredProducts_data();
void outerInGroup();
diff --git a/tests/auto/shared.h b/tests/auto/shared.h
index 6a0caaa3c..8c4689e31 100644
--- a/tests/auto/shared.h
+++ b/tests/auto/shared.h
@@ -60,7 +60,12 @@ inline SettingsPtr settings()
return SettingsPtr(new qbs::Settings(settingsDir));
}
-inline QString profileName() { return QLatin1String("qbs_autotests"); }
+inline QString profileName()
+{
+ const QString profile = QLatin1String(qgetenv("QBS_AUTOTEST_PROFILE"));
+ return !profile.isEmpty() ? profile : QLatin1String("none");
+}
+
inline QString relativeBuildDir(const QString &configurationName = QString())
{
return !configurationName.isEmpty() ? configurationName : QLatin1String("default");
diff --git a/tests/auto/tools/tst_tools.cpp b/tests/auto/tools/tst_tools.cpp
index 0ee7b18ab..dca88f438 100644
--- a/tests/auto/tools/tst_tools.cpp
+++ b/tests/auto/tools/tst_tools.cpp
@@ -52,6 +52,7 @@
#include <tools/set.h>
#include <tools/settings.h>
#include <tools/setupprojectparameters.h>
+#include <tools/stringutils.h>
#include <tools/version.h>
#include <QtCore/qdir.h>
@@ -957,6 +958,194 @@ void TestTools::set_intersects()
QVERIFY(s1.intersects(s3));
}
+void TestTools::stringutils_join()
+{
+ QFETCH(std::vector<std::string>, input);
+ QFETCH(std::string, separator);
+ QFETCH(std::string, expectedResult);
+
+ QCOMPARE(join(input, separator), expectedResult);
+}
+
+void TestTools::stringutils_join_data()
+{
+ QTest::addColumn<std::vector<std::string>>("input");
+ QTest::addColumn<std::string>("separator");
+ QTest::addColumn<std::string>("expectedResult");
+
+ QTest::newRow("data1")
+ << std::vector<std::string>()
+ << std::string()
+ << std::string();
+
+ QTest::newRow("data2")
+ << std::vector<std::string>()
+ << std::string("separator")
+ << std::string();
+
+ QTest::newRow("data3")
+ << std::vector<std::string>({"one"})
+ << std::string("separator")
+ << std::string("one");
+
+ QTest::newRow("data4")
+ << std::vector<std::string>({"one"})
+ << std::string("separator")
+ << std::string("one");
+
+
+ QTest::newRow("data5")
+ << std::vector<std::string>({"a", "b"})
+ << std::string(" ")
+ << std::string("a b");
+
+ QTest::newRow("data6")
+ << std::vector<std::string>({"a", "b", "c"})
+ << std::string(" ")
+ << std::string("a b c");
+}
+
+void TestTools::stringutils_join_empty()
+{
+ std::vector<std::string> list;
+ std::string string = join(list, std::string());
+
+ QVERIFY(string.empty());
+}
+
+void TestTools::stringutils_join_char()
+{
+ QFETCH(std::vector<std::string>, input);
+ QFETCH(char, separator);
+ QFETCH(std::string, expectedResult);
+
+ QCOMPARE(join(input, separator), expectedResult);
+}
+
+void TestTools::stringutils_join_char_data()
+{
+ QTest::addColumn<std::vector<std::string>>("input");
+ QTest::addColumn<char>("separator");
+ QTest::addColumn<std::string>("expectedResult");
+
+ QTest::newRow("data1")
+ << std::vector<std::string>()
+ << ' '
+ << std::string();
+
+ QTest::newRow("data5")
+ << std::vector<std::string>({"a", "b"})
+ << ' '
+ << std::string("a b");
+
+ QTest::newRow("data6")
+ << std::vector<std::string>({"a", "b", "c"})
+ << ' '
+ << std::string("a b c");
+}
+
+void TestTools::stringutils_startsWith()
+{
+ std::string a;
+ a = "AB";
+ QVERIFY( startsWith(a, "A") );
+ QVERIFY( startsWith(a, "AB") );
+ QVERIFY( !startsWith(a, "C") );
+ QVERIFY( !startsWith(a, "ABCDEF") );
+ QVERIFY( startsWith(a, "") );
+ QVERIFY( startsWith(a, 'A') );
+ QVERIFY( !startsWith(a, 'C') );
+ QVERIFY( !startsWith(a, char()) );
+
+ QVERIFY( startsWith(a, "A") );
+ QVERIFY( startsWith(a, "AB") );
+ QVERIFY( !startsWith(a, "C") );
+ QVERIFY( !startsWith(a, "ABCDEF") );
+ QVERIFY( startsWith(a, "") );
+
+ a = "";
+ QVERIFY( startsWith(a, "") );
+ QVERIFY( !startsWith(a, "ABC") );
+
+ QVERIFY( startsWith(a, "") );
+ QVERIFY( !startsWith(a, "ABC") );
+
+ QVERIFY( !startsWith(a, 'x') );
+ QVERIFY( !startsWith(a, char()) );
+
+ a = std::string();
+ QVERIFY( startsWith(a, "") ); // different from QString::startsWith
+ QVERIFY( !startsWith(a, "ABC") );
+
+ QVERIFY( !startsWith(a, 'x') );
+ QVERIFY( !startsWith(a, char()) );
+
+ a = u8"\xc3\xa9";
+ QVERIFY( startsWith(a, u8"\xc3\xa9") );
+ QVERIFY( !startsWith(a, u8"\xc3\xa1") );
+}
+
+void TestTools::stringutils_endsWith()
+{
+ std::string a;
+ a = "AB";
+ QVERIFY( endsWith(a, "B") );
+ QVERIFY( endsWith(a, "AB") );
+ QVERIFY( !endsWith(a, "C") );
+ QVERIFY( !endsWith(a, "ABCDEF") );
+ QVERIFY( endsWith(a, "") );
+ QVERIFY( endsWith(a, 'B') );
+ QVERIFY( !endsWith(a, 'C') );
+ QVERIFY( !endsWith(a, char()) );
+
+ QVERIFY( endsWith(a, "B") );
+ QVERIFY( endsWith(a, "AB") );
+ QVERIFY( !endsWith(a, "C") );
+ QVERIFY( !endsWith(a, "ABCDEF") );
+ QVERIFY( endsWith(a, "") );
+
+ a = "";
+ QVERIFY( endsWith(a, "") );
+ QVERIFY( !endsWith(a, "ABC") );
+ QVERIFY( !endsWith(a, 'x') );
+ QVERIFY( !endsWith(a, char()) );
+
+ QVERIFY( endsWith(a, "") );
+ QVERIFY( !endsWith(a, "ABC") );
+
+ a = std::string();
+ QVERIFY( endsWith(a, "") ); // different from QString::endsWith
+ QVERIFY( !endsWith(a, "ABC") );
+
+ QVERIFY( !endsWith(a, 'x') );
+ QVERIFY( !endsWith(a, char()) );
+
+ a = u8"\xc3\xa9";
+ QVERIFY( endsWith(a, u8"\xc3\xa9") );
+ QVERIFY( !endsWith(a, u8"\xc3\xa1") );
+}
+
+void TestTools::stringutils_trimmed()
+{
+ std::string a;
+ a = "Text";
+ QCOMPARE(a, std::string("Text"));
+ QCOMPARE(trimmed(a), std::string("Text"));
+ QCOMPARE(a, std::string("Text"));
+ a = " ";
+ QCOMPARE(trimmed(a), std::string(""));
+ QCOMPARE(a, std::string(" "));
+ a = " a ";
+ QCOMPARE(trimmed(a), std::string("a"));
+
+ a = "Text";
+ QCOMPARE(trimmed(std::move(a)), std::string("Text"));
+ a = " ";
+ QCOMPARE(trimmed(std::move(a)), std::string(""));
+ a = " a ";
+ QCOMPARE(trimmed(std::move(a)), std::string("a"));
+}
+
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
diff --git a/tests/auto/tools/tst_tools.h b/tests/auto/tools/tst_tools.h
index cb6828ee8..e80d5fbf1 100644
--- a/tests/auto/tools/tst_tools.h
+++ b/tests/auto/tools/tst_tools.h
@@ -85,6 +85,15 @@ private slots:
void set_initializerList();
void set_intersects();
+ void stringutils_join();
+ void stringutils_join_data();
+ void stringutils_join_empty();
+ void stringutils_join_char();
+ void stringutils_join_char_data();
+ void stringutils_startsWith();
+ void stringutils_endsWith();
+ void stringutils_trimmed();
+
private:
QString setupSettingsDir1();
QString setupSettingsDir2();
diff --git a/tests/manual/minimumSystemVersion/main.cpp b/tests/manual/minimumSystemVersion/main.cpp
deleted file mode 100644
index 678ad1a06..000000000
--- a/tests/manual/minimumSystemVersion/main.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <stdio.h>
-#include <QCoreApplication>
-#include <QDebug>
-#include <QProcess>
-
-int main(int argc, char *argv[])
-{
- QCoreApplication a(argc, argv);
- QTextStream out(stdout);
-#ifdef Q_OS_WIN32
- out.setIntegerBase(16);
- out.setNumberFlags(QTextStream::ShowBase);
-#ifdef WINVER
- out << "WINVER = " << WINVER;
-#else
- out << "WINVER is not defined";
-#endif
- endl(out);
-
- QProcess dumpbin;
- dumpbin.start("dumpbin", QStringList() << "/HEADERS" << a.applicationFilePath());
- dumpbin.waitForStarted();
-
- out << "dumpbin says...";
- endl(out);
-
- while (dumpbin.waitForReadyRead()) {
- while (dumpbin.canReadLine()) {
- QString line = dumpbin.readLine();
- if (line.contains("version"))
- out << line.trimmed() << "\n";
- }
- }
-
- dumpbin.waitForFinished();
-#endif
-
-#ifdef Q_OS_MACX
- out.setIntegerBase(10);
-
- // This gets set by -mmacosx-version-min. If left undefined, defaults to the current OS version.
- out << "__MAC_OS_X_VERSION_MIN_REQUIRED = " << __MAC_OS_X_VERSION_MIN_REQUIRED;
- endl(out);
-
- // This gets determined by the SDK version you're compiling with
- out << "__MAC_OS_X_VERSION_MAX_ALLOWED = " << __MAC_OS_X_VERSION_MAX_ALLOWED;
- endl(out);
-
- bool print = false;
- QProcess otool;
- otool.start("otool", QStringList() << "-l" << a.applicationFilePath());
- otool.waitForStarted();
-
- out << "otool says...";
- endl(out);
-
- while (otool.waitForReadyRead()) {
- while (otool.canReadLine()) {
- QString line = otool.readLine();
- if (line.contains("LC_VERSION_MIN_MACOSX"))
- print = true;
-
- if (print && (line.contains("version") || line.contains("sdk"))) {
- out << line.trimmed();
- endl(out);
-
- if (line.contains("sdk"))
- print = false;
- }
- }
- }
-#endif
-
- return 0;
-}
diff --git a/tests/manual/minimumSystemVersion/minimumSystemVersion.qbs b/tests/manual/minimumSystemVersion/minimumSystemVersion.qbs
deleted file mode 100644
index 10cb9d7be..000000000
--- a/tests/manual/minimumSystemVersion/minimumSystemVersion.qbs
+++ /dev/null
@@ -1,76 +0,0 @@
-import qbs 1.0
-
-Project {
- // no minimum versions are specified so the profile defaults will be used
- QtApplication {
- name: "unspecified"
- files: "main.cpp"
- consoleApplication: true
-
- Properties {
- condition: qbs.targetOS.contains("darwin")
- cpp.frameworks: "Foundation"
- }
- }
-
- // no minimum versions are specified, and explicitly set to undefined in
- // case the profile has set it
- QtApplication {
- name: "unspecified-forced"
- files: "main.cpp"
- consoleApplication: true
- cpp.minimumWindowsVersion: undefined
- cpp.minimumMacosVersion: undefined
- cpp.minimumIosVersion: undefined
- cpp.minimumAndroidVersion: undefined
-
- Properties {
- condition: qbs.targetOS.contains("darwin")
- cpp.frameworks: "Foundation"
- }
- }
-
- // a specific version of the operating systems is specified
- // when the application is run its output should confirm
- // that the given values took effect
- QtApplication {
- condition: qbs.targetOS.contains("windows") || qbs.targetOS.contains("macos")
- name: "specific"
- files: "main.cpp"
- consoleApplication: true
-
- Properties {
- condition: qbs.targetOS.contains("windows")
- cpp.minimumWindowsVersion: "6.0"
- }
-
- Properties {
- condition: qbs.targetOS.contains("macos")
- cpp.frameworks: "Foundation"
- cpp.minimumMacosVersion: "10.6"
- }
- }
-
- // non-existent versions of Windows should print a QBS warning
- // (but will still compile and link since we avoid passing a
- // bad value to the linker)
- QtApplication {
- condition: qbs.targetOS.contains("windows")
- name: "fakewindows"
- files: "main.cpp"
- consoleApplication: true
- cpp.minimumWindowsVersion: "5.3"
- }
-
- // just to make sure three-digit minimum versions work on macOS
- // this only affects the value of __MAC_OS_X_VERSION_MIN_REQUIRED,
- // not the actual LC_VERSION_MIN_MACOSX command which is limited to two
- QtApplication {
- condition: qbs.targetOS.contains("macos")
- name: "macappstore"
- files: "main.cpp"
- consoleApplication: true
- cpp.frameworks: "Foundation"
- cpp.minimumMacosVersion: "10.6.8"
- }
-}
diff --git a/tests/manual/run-qbs-tests.bat b/tests/manual/run-qbs-tests.bat
deleted file mode 100644
index 21da31771..000000000
--- a/tests/manual/run-qbs-tests.bat
+++ /dev/null
@@ -1,8 +0,0 @@
-@echo off
-
-:: Usage
-:: run-qbs-tests.bat qbs debug release profile:x64 platform:clang
-
-for /D %%i in (%CD%\*) do (
- cmd /C "cd %%i && cmd /C %*"
-)
diff --git a/tests/manual/run-qbs-tests.sh b/tests/manual/run-qbs-tests.sh
deleted file mode 100755
index 3c54c9c6c..000000000
--- a/tests/manual/run-qbs-tests.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-# Usage
-# ./run-qbs-tests.sh qbs debug release profile:x64 platform:clang
-
-for i in *; do
- [ -d "$i" ] || continue
- cd "$i"
- "$@"
- cd ..
-done